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

Posted

技术标签:

【中文标题】如何在 TypeScript 中使装饰器类型安全?【英文标题】:How do I make a decorator typesafe in 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() 类型的代码示例?

【问题讨论】:

【参考方案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

以上是关于如何在 TypeScript 中使装饰器类型安全?的主要内容,如果未能解决你的问题,请参考以下文章

Typescript:使用装饰器时的类型推断

TypeScript React HOC 返回类型与装饰器兼容

使用 TypeScript 装饰器扩展 ES6 类时扩展类型

typeScript 装饰器

Decorators装饰器——TypeScript

TypeScript装饰器(decorators)