如何在 TypeScript 中使装饰器类型安全?

How do I make a decorator typesafe in TypeScript?

示例:

class Parent {
  parentMethod() {
    // ...
  }
}

@Hooks({
  // Only methods from the `Child` class (including inherited methods) must be allowed here
  childMethod: [/* ... */],
  parentMethod: [/* ... */]
})
class Child extends Parent {
  childMethod() {
    // ...
  }
}

@Hooks() 装饰器将对象作为参数。在此对象中,键是 Child class 中的方法名称。如何使 @Hooks() 装饰器类型安全? 您能否提供 @Hooks()?

类型的代码示例

检查它是否适合你

interface ClassType<T> extends Function {
    new (...args: any[]): T;
}

type MethodKeys<T> = ({
  [K in keyof T]: T[K] extends Function ? K : never
})[keyof T]

type HooksOptions<T> = {
  [K in MethodKeys<T>]: []
}

function Hooks<T>(_options: HooksOptions<T>) {
  return function (ctor: ClassType<T>): ClassType<T> {
    return ctor
  }
}

class Parent {
  parentMethod() {
    // ...
  }
}

@Hooks<Child>({
  childMethod: [/* ... */],
  parentMethod: [/* ... */]
})
class Child extends Parent {
  childMethod() {
    // ...
  }
}

@Hooks("invalid-value") // error
class Child2 extends Parent {
  childMethod() {
    // ...
  }
}


@Hooks<Child3>({
  c: [], // error
  childMethod: [/* ... */],
  childMethod2: [/* ... */], // error
  parentMethod: [/* ... */]
})

class Child3 extends Parent {
  public c: string = ''
  childMethod() {
    // ...
  }
}

Playground

在 typescript 中没有某个 class 方法的类型(从未听说过其他语言中的此类类型,TBH), 您可以为函数签名指定类型。

编辑: 好吧,显然有一些我不知道的技巧 ,正如其他答案中所见.. 但是,如果您对快速简单但有限的解决方案感兴趣:

例如:

let myAdd: (baseValue: number, increment: number) => number = (baseValue, increment) => baseValue + increment

有关类型化函数的更多信息,请点击此处: https://www.typescriptlang.org/docs/handbook/functions.html

如果这样就可以在装饰器的声明中使用类型化签名,如下所示:

Interface ObjectInterface {
   ChildMethods: {(param) => void} [], //or whatever fuction signature meets your need, this is just an example.
   ParentMethods: {(param) => void}[]
}

//this is the decorator declaration
function Hooks(obj: ObjectInterface) {
  return function (
    target,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    // what ever implementation you have in hooks...
  };
}

这种方式将非(param) => void类型的函数传递给装饰器时会导致编译错误。

更多信息在这里: https://www.typescriptlang.org/docs/handbook/decorators.html

这里是类型化函数数组,就像我在 ObjectInterface 上所做的那样: A Typed array of functions

我找到了解决方案

interface Type<T> extends Function {
    new (...args: any[]): T;
}

type HookMap<T> = {
  [func in keyof T]?: any[];
};

function Hooks<T>(hookMap: HookMap<T>) {
  return (clazz: Type<T>): void => {
    // ...
  };
}

class Parent {
  parentMethod() {
    // ...
  }
}

@Hooks({
  childMethod: [/* ... */],
  parentMethod: [/* ... */]
})
class Child extends Parent {
  childMethod() {
    // ...
  }
}

Playground