TypeScript属性装饰器,采用装饰属性类型的参数

时间:2020-03-08 18:18:49

标签: typescript typescript-generics typescript-decorator

我正在尝试编写一个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调用中明确指定参数?

1 个答案:

答案 0 :(得分:1)

我要说的主要问题是,当使用咖喱装饰器函数时,无法推断出K类型参数(或T类型参数)的规范。如果您的咖喱类泛型函数的类型如下:

declare const curryBad: <T, U>(t: T) => (u: U) => [T, U]

在调用TU时,编译器将尝试进行推断。假设您致电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; 
}

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

Playground link to code

相关问题