【问题标题】:How do I make a decorator typesafe in TypeScript?如何在 TypeScript 中使装饰器类型安全?
【发布时间】:2021-01-24 22:22:40
【问题描述】:

例子:

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 类的方法名。如何使 @Hooks() 装饰器类型安全? 您能否提供一个包含@Hooks() 类型的代码示例?

【问题讨论】:

    标签: typescript types decorator


    【解决方案1】:

    如果它适合你,请检查它

    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

    【讨论】:

    • 我喜欢你的解决方案,但如果没有指定 @Hooks&lt;GenericType&gt; 的泛型类型,TypeScript 不会强制你指定它,@Hooks 将接受 any 参数为有效。见example
    • @Mike 回答已更新。 TS 现在将强制通过泛型
    【解决方案2】:

    打字稿中某个类的方法没有类型(在其他语言中也没有听说过这种类型以及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) =&gt; void 类型的函数传递给装饰器时会导致编译错误。

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

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

    【讨论】:

      【解决方案3】:

      我找到了解决办法

      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

      【讨论】:

      • 好吧,这将允许在 Child 和 Parent 类中声明的属性在装饰器对象中使用。在问题中,解决方案应该只包含方法名称。
      • @ShivamSingla 是的,我同意。但我无法将您的 MethodKeys 类型集成到我的解决方案中。见example
      猜你喜欢
      • 2018-06-30
      • 1970-01-01
      • 2022-08-15
      • 1970-01-01
      • 2016-11-13
      • 2019-07-23
      • 2015-10-23
      • 2018-06-08
      • 2017-11-15
      相关资源
      最近更新 更多