繁体   English   中英

TypeScript 继承静态方法强制转换

[英]TypeScript inheritance static methods cast

我有一个包含两个类 BaseModel 和 HotelModel 的 TypeScript 项目。 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
  }

}

这是 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')
    }
  }

}

现在,当我调用 HotelModel.findById(1) 时,我想收到成员类 (HotelModel) 的一个实例,这可能吗? 我怎样才能做到这一点?

- - - 更新 - - -

根据建议,这就是我得到的

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
  }

}

然后我有一个 HotelService 类,它扩展了通用类并继承了所有方法

export default class HotelService extends Service<HotelModel> {

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

}

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

好吧,花了很多时间,但我找到了一个“优雅”(至少对我而言)的解决方案来解决问题

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)

操场

谢谢

我相信这就是你所追求的

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);

游乐场链接

那么,这里发生了什么?

我们正在使用 TypeScript 的功能来指定函数和方法隐式接收的this值的类型。

由于我们在static方法中,因此this类型指的是类本身的类型。 该类型是我们可以用new调用的东西,也就是说它是一个构造函数。

但是,我们想要捕获派生类的实际类型。 为此,我们声明了一个泛型类型T ,它表示派生类在我们使用new调用它时返回的任何内容。 然后我们声明this是一个创建T的构造函数。 然而,这样做时我们失去了对基类静态成员的访问权限,我们必须用交集重新添加它们。

最后,当我们调用Hotel.findById ,打字稿推断Ttypeof Hotel ,因为typeof Hotel是值的类型findById被称为上。

注意:通常, findByIdthis类型写起来更简单,即(new (...args: any[]) => T) & typeof BaseModel但在这种情况下,您的派生类Hotel有一个不兼容的构造函数参数列表。 我使用Pick<typeof BaseModel, keyof typeof BaseModel>作为获取包含typeof BaseModel所有成员(调用和构造签名除外)的类型的快速而肮脏的方法。

重载酒店静态函数

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

我不习惯打字稿,所以也许有人可以帮助我,但在您更新之后,我认为您需要传递实际的构造函数值而不仅仅是一个类型

这是一个例子

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())

操场

我在 super 内部传递实际类并在 getFindId() 中使用它来返回此类的实例。

暂无
暂无

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

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