我正在尝试编写一个Default
装饰器,以使其起作用:
class A {
@Default(() => new Date())
test: Date;
}
我正在尝试实现强类型化,以便装饰器参数要么是(1)装饰属性类型的,要么是(2)无参数函数返回装饰属性类型的值。
我尝试了以下操作:
type DefaultParam<T> = T | (() => T);
export function Default<K extends string, T extends Record<K, V>, V>(param: DefaultParam<V>): (t: T, p: K) => void {
return (target: T, propertyKey: K) => {
// Probably irrelevant
};
}
但是,此操作失败:
Argument of type 'A' is not assignable to parameter of type 'Record<string, Date>'.
Index signature is missing in type 'A'.
但是,指定类型参数可以按预期工作:
class A {
@Default<'test', A, Date>(() => new Date())
test: Date;
}
是否有一种编写修饰符的方法,以便推理能够按预期进行,从而避免在Default
调用中明确指定参数?
答案 0 :(得分:1)
我要说的主要问题是,当使用咖喱装饰器函数时,无法推断出K
类型参数(或T
类型参数)的规范。如果您的咖喱类泛型函数的类型如下:
declare const curryBad: <T, U>(t: T) => (u: U) => [T, U]
在调用T
和U
时,编译器将尝试进行推断。假设您致电curryBad(1)
。 1
值导致T
被推断为number
。但是这里没有类型U
的值,因此U
的类型推断失败,并且变为unknown
。因此,curryBad(1)
的结果为(u: unknown) => [number, unknown]
:
const bad = curryBad(1)(""); // [number, unknown]
尽管从理论上说编译器可能推迟推断U
直到调用返回的函数,这并不是不可想象的,但实际上这不是它的工作方式。相反,您可以以以下方式开始编写函数的签名:不要将U
声明为初始函数的参数;声明它为返回函数的参数:
declare const curryGood: <T>(t: T) => <U>(u: U) => [T, U]
现在调用curryGood(1)
将返回类型<U>(u: U) => [number, U]
的值,这可能是您想要的:
const good = curryGood(1)(""); // [number, string]
请牢记这一点,建议您移动K
参数。另外,我不确定您是否真的需要T
作为其自己的泛型类型; Record<K, V>
可能足够好。但是,如果您确实需要它,则还应将其与K
一起移动:
export function Default<V>(
param: DefaultParam<V>): <K extends PropertyKey>(t: Record<K, V>, p: K) => void {
return <K extends PropertyKey>(target: Record<K, V>, propertyKey: K) => {
};
}
现在,您的装饰器应该可以按预期工作:
class Good {
@Default(() => new Date()) // no error
test!: Date;
}
class Bad {
@Default(123) // error! Date is not number
test!: Date;
}
好的,希望能有所帮助;祝你好运!