网站搜索

在 TypeScript 中将属性装饰器限制为特定配置类型


注意:此问题仅将 Property 装饰器扩展到特定属性类型

--

我的目标是设计一个简单的ORM,其中模型可以这样定义:

@model.model()
class User extends model.Model {

  @model.attr(model.StringType)
  user: string;

  @model.attr(model.StringType)
  age: number;

}

在这个模型中,我希望编译器抛出类型错误,因为 StringType 不能应用于类型为 number 的属性。然而,我无法让它正常工作——没有抛出任何错误。

我到目前为止的代码:

interface Constructor<T> {
  new (...args: any[]): T;
}

abstract class BaseType<T> {}

class StringType extends BaseType<string> {}

type RecordKey = string | symbol;

export function attr<T>(type: Constructor<BaseType<T>>) {
  return <K extends RecordKey, C extends Record<K, T>>(ctor: C, key: K) => {
    console.log(ctor, key);
  }
}

关于这段代码为什么不起作用的一些提示在这里将非常有用。

答案#1

Typescript 是结构化类型,这是整个答案的关键。在这种情况下,这意味着任何普通对象都可以是BaseType<T>,因为它没有字段/方法。请注意,在 playground 中,推断类型在@model.attr(model.StringType)中的T显示为{}。

以下赋值完全可以(并且 number 可以是任何值):

abstract class BaseType<T> { }
const t: BaseType<number> = { };

要要求指定泛型,请尝试:

function attr<T = never>(type: Constructor<BaseType<T>>) { ... }
// ...
  @model.attr<string>(model.StringType)
  user: string

但请注意,它将允许 @model.attr<number>(model.StringType),因为再次强调,任何对象都是 BaseType<number>。

如果你的 BaseType 接口尚未完成,继续完善它,你可能会发现 TypeScript 有足够的信息来进行推断。例如(完全是随意的,但可以让你理解概念并测试推断):

abstract class BaseType<T> {
  deserialize: (s: string) => T;
}