在 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;
}
