多用途 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;
}

Playground

如果我明确使用将泛型 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;
}

看起来不错。


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

Playground link to code