TypeScript:区分类和类的实例吗?

时间:2020-05-05 00:07:48

标签: typescript typescript-generics

在TypeScript中,我有一个用于存储对象集合的类。

class Collection<T extends Model> extends Array<T> {
  options: CollectionOptions;

  constructor(options: CollectionOptions) {
    super();

    this.options = options;
  }

  add(model: T): T {
    this.push(model);
    return model;
  }
}

我还有一种创建新集合的机制:

function createCollection<T extends Model>(
  _ModelClass: T, options: CollectionOptions
): Collection<T> {
  const collection: Collection<T> = new Collection(options);

  return collection;
}

我有一些非常可重用的通用选项和一个非常基本的模型类:

type CollectionOptions = {
  key: string
}

class Model {
  [key: string]: any;
}

现在,当我创建一个具体的类以将其用于:

class User extends Model {
  id: number = 0
}

如果我这样实例化我的收藏集:

const model = createCollection(new User(), { key: 'id' });

那么这是允许的:

model.add(new User());

但是如果我发送User类而不是实例:

const model = createCollection(User, { key: 'id' });

然后我尝试执行model.add(new User())时收到此错误:

“用户”类型的参数不能分配给“类型用户”类型的参数。 类型“用户”中缺少属性“原型”,但类型“用户类型”中必需。ts(2345)

有没有一种方法可以修改我的createCollection函数,以使其返回类型明确是 instances 而不是 classes 的集合?

1 个答案:

答案 0 :(得分:1)

_ModelClass的{​​{1}}参数在JavaScript中完全未使用,对吗?似乎其唯一目的是为TypeScript编译器提供泛型参数createCollection的推断站点。如果是这样,也许“正确”的解决方案是完全消除该论点:

T

并让调用者手动将function createCollection<T extends Model>( options: CollectionOptions ): Collection<T> { const collection: Collection<T> = new Collection(options); return collection; } 参数的类型指定为T

User

或让它们将返回值分配给类型为const model = createCollection<User>({ key: 'id' }); // manually specified model.add(new User()); // okay 的手动注释变量,并让编译器从上下文中根据所需返回类型推断Collection<User>T

User

如果您想保留虚拟const model2: Collection<User> = createCollection({ key: 'id' }); model2.add(new User()); // okay 参数,而您的目的是传递类构造函数而不是类 instance ,则{{1 }}签名错误。像您的_ModelClass示例那样的类声明引入了与类实例的类型相对应的名为接口类型和与类构造函数有关的名为 value 。尽管名称相同,但它们是不同的。名为createCollection()的构造函数值本身不是User类型的。

名为User的类型有点像User;具有数字User属性和许多其他可能属性的对象,而值{id: number; [key: string]: any;}(即id)的类型有点像User ;一个newable对象,当使用typeof User调用时,该对象产生类型为new () => User的值。

如果您想详细了解类型和值之间的区别以及同名如何根据上下文引用类型和值,请查看another lengthy monologue of mine on this subject

无论如何,如果您的目的是接受构造函数而不是实例,则应将User参数的类型注释为new而不是_ModelClass,这意味着“一个可构造的构造函数,它接受一些我们不关心的参数,并产生类型为T的值”:

new (...args: any) => T

然后您的函数将接受T构造函数:

function createCollection<T extends Model>(
    _ModelClass: new (...args: any) => T,
    options: CollectionOptions
): Collection<T> {
    const collection: Collection<T> = new Collection(options);
    return collection;
}

但是抱怨User实例:

const model = createCollection(User, { key: 'id' });
model.add(new User()); // okay

如果出于某种原因您希望编译器接受 实例构造函数作为User,则可以将该参数注释为{{3 }}实例和构造函数类型:

const modelBad = createCollection(new User(), { key: 'id' }); // error!
// Type 'User' is not assignable to type 'new (...args: any) => Model`

然后它将同时起作用:

_ModelClass

好的,希望能有所帮助;祝你好运!

union