多用途 TypeScript 装饰器
Multi purpose TypeScript decorator
我正在尝试定义一个可以在 class 和 属性 上使用的装饰器。代码有效,但我无法使类型定义有效。我目前有以下内容:
type Decorator<T> = T extends Function
? (target: T) => T
: (target: T, propertyKey: string) => void;
function Schema<T>(): Decorator<T> {
return (target: T, propertyKey?: string) => {
if (target instanceof Function) {
return target;
}
};
}
@Schema()
export default class MyClass {
@Schema()
bool: boolean;
}
如果我明确使用将泛型 T
设置为 typeof BooleanProperty
,它会起作用。但是,我希望 Typescript 能够推断类型并停止抱怨。
我也尝试了其他选项,例如函数重载,但没有成功。
我该如何解决这个问题?
您可能希望装饰器工厂是非通用的,并让它生成通用装饰器。现在你的装饰器工厂是通用的,returns 是一个非通用的装饰器。就是这两者的区别:
// bad
declare const genericFactoryForSpecificFunction: <T>() => (x: T) => T;
const oops = genericFactoryForSpecificFunction()(123); // unknown
// good
declare const specificFactoryForGenericFunction: () => <T>(x: T) => T;
const okay = specificFactoryForGenericFunction()(123); // 123
在糟糕的情况下,当您调用 genericFactoryForSpecificFunction()
时,编译器必须推断出 T
,但是在该表达式中没有 T
类型的值可供它查询。所以它选择 unknown
,这意味着后续调用参数 123
returns unknown
。在好的情况下,编译器推迟 T
的推断,直到调用 specificFactoryForGenericFunction()
返回的函数。这让编译器实际上使用 T
类型的值来推断 T
,所以你得到 123
。
进行此更改后,您需要 Decorator
类型是表示通用函数调用签名的特定类型。我想可以将您的条件类型转换为该形式,但它有点混乱并且使用了剩余元组:
type PossibleDecorator = <T>(
target: T,
...args: T extends Function ? [] : [string?]
) => T extends Function ? T : void;
declare const pd: PossibleDecorator;
pd(RegExp); // okay, class decorator
pd({a: ""}, "a") // okay, prop decorator
pd(RegExp, "oops"); // error, tried to pass prop to class decorator
我想说多调用签名重载会更好,并且它具有相同的行为:
type Decorator = {
<T extends new (...args: any) => any>(ctor: T): T; // class decorator
<T, K extends keyof T>(proto: T, member: K): void; // prop decorator
}
declare const d: Decorator;
d(RegExp); // okay, class decorator
d({ a: "" }, "a") // okay, prop decorator
d(RegExp, "oops"); // error, tried to pass prop to class decorator
因此,您的示例代码将变成如下所示:
function Schema(): Decorator {
return (target: any, propertyKey?: string) => {
if (target instanceof Function) {
return target;
}
return;
};
}
@Schema()
export default class MyClass {
@Schema()
bool: boolean = false;
}
看起来不错。
好的,希望对您有所帮助;祝你好运!
我正在尝试定义一个可以在 class 和 属性 上使用的装饰器。代码有效,但我无法使类型定义有效。我目前有以下内容:
type Decorator<T> = T extends Function
? (target: T) => T
: (target: T, propertyKey: string) => void;
function Schema<T>(): Decorator<T> {
return (target: T, propertyKey?: string) => {
if (target instanceof Function) {
return target;
}
};
}
@Schema()
export default class MyClass {
@Schema()
bool: boolean;
}
如果我明确使用将泛型 T
设置为 typeof BooleanProperty
,它会起作用。但是,我希望 Typescript 能够推断类型并停止抱怨。
我也尝试了其他选项,例如函数重载,但没有成功。
我该如何解决这个问题?
您可能希望装饰器工厂是非通用的,并让它生成通用装饰器。现在你的装饰器工厂是通用的,returns 是一个非通用的装饰器。就是这两者的区别:
// bad
declare const genericFactoryForSpecificFunction: <T>() => (x: T) => T;
const oops = genericFactoryForSpecificFunction()(123); // unknown
// good
declare const specificFactoryForGenericFunction: () => <T>(x: T) => T;
const okay = specificFactoryForGenericFunction()(123); // 123
在糟糕的情况下,当您调用 genericFactoryForSpecificFunction()
时,编译器必须推断出 T
,但是在该表达式中没有 T
类型的值可供它查询。所以它选择 unknown
,这意味着后续调用参数 123
returns unknown
。在好的情况下,编译器推迟 T
的推断,直到调用 specificFactoryForGenericFunction()
返回的函数。这让编译器实际上使用 T
类型的值来推断 T
,所以你得到 123
。
进行此更改后,您需要 Decorator
类型是表示通用函数调用签名的特定类型。我想可以将您的条件类型转换为该形式,但它有点混乱并且使用了剩余元组:
type PossibleDecorator = <T>(
target: T,
...args: T extends Function ? [] : [string?]
) => T extends Function ? T : void;
declare const pd: PossibleDecorator;
pd(RegExp); // okay, class decorator
pd({a: ""}, "a") // okay, prop decorator
pd(RegExp, "oops"); // error, tried to pass prop to class decorator
我想说多调用签名重载会更好,并且它具有相同的行为:
type Decorator = {
<T extends new (...args: any) => any>(ctor: T): T; // class decorator
<T, K extends keyof T>(proto: T, member: K): void; // prop decorator
}
declare const d: Decorator;
d(RegExp); // okay, class decorator
d({ a: "" }, "a") // okay, prop decorator
d(RegExp, "oops"); // error, tried to pass prop to class decorator
因此,您的示例代码将变成如下所示:
function Schema(): Decorator {
return (target: any, propertyKey?: string) => {
if (target instanceof Function) {
return target;
}
return;
};
}
@Schema()
export default class MyClass {
@Schema()
bool: boolean = false;
}
看起来不错。
好的,希望对您有所帮助;祝你好运!