简体   繁体   English

TypeScript 继承静态方法强制转换

[英]TypeScript inheritance static methods cast

I have a TypeScript project with two classes BaseModel and HotelModel.我有一个包含两个类 BaseModel 和 HotelModel 的 TypeScript 项目。 The HotelModel extends the BaseModel class that provides some static methods like findById, all, etc.. HotelModel 扩展了 BaseModel 类,该类提供了一些静态方法,如 findById、all 等。

export default class BaseModel {

  private collection:string
  _id:string | undefined

  constructor (collection:string) {
    this.collection = collection
  }

  static getCollectionName () {
    return this.prototype.constructor.name.toString().toLowerCase() + 's'
  }

  static async findById (id:string) {
    const connection = await getConnection()
    const hotel = await connection.collection(this.getCollectionName())
    .findOne({
      _id: new mongodb.ObjectId(id)
    })
    if (!hotel) {
      throw new ResourceNotFound('Hotel not found with the given id' + id)
    }
    return hotel
  }

}

and this is the HotelClass这是 HotelClass

import BaseModel from './baseModel'
import IHotel from '../interfaces/IHotel'

import ValidationException from '../../exceptions/ValidationException'

export default class Hotel extends BaseModel {

  name:string
  status:string
  metadata:object

  constructor (hotel:IHotel) {
    super('hotels')
    this.name = hotel.name
    this.status = hotel.status
    this.metadata = hotel.metadata
  }

  validate () {
    if (!this.name || this.name === '') {
      throw new ValidationException('Name field is required')
    }
  }

}

Now when i call HotelModel.findById(1) i would like to receive back an istance of the member class (HotelModel) is this possible?现在,当我调用 HotelModel.findById(1) 时,我想收到成员类 (HotelModel) 的一个实例,这可能吗? How can i achieve that?我怎样才能做到这一点?

------UPDATE------ - - - 更新 - - -

based on suggestion this is what i got根据建议,这就是我得到的

export default class Service<T> {

  private collection:string

  constructor (collection:string) {
    this.collection = collection
  }

  async findById (id:string) {
    const connection = await getConnection()
    const model = await connection.collection(this.collection)
      .findOne({
        _id: new mongodb.ObjectId(id)
      }) as T
    if (!model) {
      throw new ResourceNotFound('Model not found with the given id' + id)
    }
    return model
  }

}

then i have a HotelService class that extends the generic one and inherits all the methods然后我有一个 HotelService 类,它扩展了通用类并继承了所有方法

export default class HotelService extends Service<HotelModel> {

  public constructor () {
    super('hotels')
  }

}

------UPDATE 2------ ------更新 2------

Well, it took quite a lot of time but I found an "elegant" (at least to me) solution to solve the problem好吧,花了很多时间,但我找到了一个“优雅”(至少对我而言)的解决方案来解决问题

class QueryBuilder {

    private modelType: typeof BaseModel;

    constructor (modelType: typeof BaseModel) {
        this.modelType = modelType
    }

    data:Array<any> = [
        { id: '1', name: 'Jane' },
        { id: '2', name: 'John' },
        { id: '3', name: 'Mark' }
    ]

    findById (id:string) {
        // fake database call
        const data = this.data.find(r => r.id === id)
        // "cast" the database object to the required type
        let model:any = new this.modelType()
        model.fill(data)
        return model
    }
    
}

class BaseModel {

    private id:string | undefined

    constructor () {}

    static findById () {
        return new QueryBuilder(this)
            .findById('1')
    }

    public save () {
        console.log('calling save')
        this.id = '123456'
    }

    public fill (data:any) {
    }

}

class HotelModel extends BaseModel {

    public name:string | undefined

    constructor (
        name:string
    ) {
        super()
    }

}

let h:HotelModel = HotelModel.findById()
h.name = 'test name'
h.save()
console.log(h)
console.log(h instanceof HotelModel)

PlayGround 操场

Thank you谢谢

I believe this is what you are after我相信这就是你所追求的

export default class BaseModel {
  collection: string
  _id: string | undefined

  constructor(collection: string) {
    this.collection = collection;
  }

  static get collectionName() {
    return this.name.toLowerCase() + 's';
  }

  static async findById<T extends BaseModel>(
    this: (new (...args: any[]) => T) & Pick<typeof BaseModel, keyof typeof BaseModel>,
    id: string
  ): Promise<T> {
    const connection = await getConnection();
    const model = await connection.collection(this.collectionName)
      .findOne({
        _id: new mongodb.ObjectId(id)
      });
    if (!model) {
      throw new ResourceNotFound(`${this.collectionName} not found with the given id ${id}`);
    }
    return model as T;
  }
}

export default class Hotel extends BaseModel { ... }

const hotel = await Hotel.findOneBy('1');
console.log(hotel.name);
console.log(hotel.status);

Playground Link 游乐场链接

So, what's going on here?那么,这里发生了什么?

We are using using TypeScript's ability to specify the type of the this value that functions and methods implicitly receive.我们正在使用 TypeScript 的功能来指定函数和方法隐式接收的this值的类型。

Since we are in a static method, the this type refers to the type of the class itself.由于我们在static方法中,因此this类型指的是类本身的类型。 That type is something we can call with new which is to say it is a constructor.该类型是我们可以用new调用的东西,也就是说它是一个构造函数。

However, we want to capture the actual type of the derived class.但是,我们想要捕获派生类的实际类型。 To this end, we declare a generic type, T , that represents whatever a derived class returns when we call it with new .为此,我们声明了一个泛型类型T ,它表示派生类在我们使用new调用它时返回的任何内容。 Then we state that this is a constructor that creates T s.然后我们声明this是一个创建T的构造函数。 We've lost access to the static members of the base class in doing so, however, and we have to add them back in with an intersection.然而,这样做时我们失去了对基类静态成员的访问权限,我们必须用交集重新添加它们。

Finally, when we call Hotel.findById , TypeScript infers T from typeof Hotel because typeof Hotel is the type of the value findById is being called on.最后,当我们调用Hotel.findById ,打字稿推断Ttypeof Hotel ,因为typeof Hotel是值的类型findById被称为上。

Note: Normally, the this type for findById would be simpler to write, ie (new (...args: any[]) => T) & typeof BaseModel but in this case, your derived class Hotel has a constructor with an incompatible argument list.注意:通常, findByIdthis类型写起来更简单,即(new (...args: any[]) => T) & typeof BaseModel但在这种情况下,您的派生类Hotel有一个不兼容的构造函数参数列表。 I used Pick<typeof BaseModel, keyof typeof BaseModel> as a quick and dirty way to obtain a type containing all of the members of typeof BaseModel except call and construct signatures.我使用Pick<typeof BaseModel, keyof typeof BaseModel>作为获取包含typeof BaseModel所有成员(调用和构造签名除外)的类型的快速而肮脏的方法。

Overload static function of Hotel重载酒店静态函数

  static async findById (id:string) {
    const data = await BaseModel.findById(id)
    return new Hotel(data)
  }

I'm not used to typescript so maybe someone can help me but following your update I think you need to pass the actual constructor value and not just a type我不习惯打字稿,所以也许有人可以帮助我,但在您更新之后,我认为您需要传递实际的构造函数值而不仅仅是一个类型

Here is an example这是一个例子

class Service {
   private collection: string
   private Model: any

   constructor (Model: any, collection: string) {
       this.collection = collection
       this.Model = Model
   }

   findById (id:string) {
    console.log(this.collection)
    return new this.Model(id)
  }
}

class HotelModel {
    public id: string
    constructor (id: string) {
        this.id = id
    }

    test () {
        return '1'
    }
}

class HotelService extends Service {
    constructor () {
        super(HotelModel, 'hotels')
    }
}

const hotelService = new HotelService()
const hotel = hotelService.findById('1')

console.log(hotel.test())

Playground 操场

I'm passing the actual class inside super and use it inside getFindId() to return the instance of this class.我在 super 内部传递实际类并在 getFindId() 中使用它来返回此类的实例。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM