区分打字稿类型中的修饰类方法

Ere*_*CAY 3 typescript typescript-generics typescript-typings

我想创建一个泛型类型,它仅从类定义中选择修饰方法。

function test(ctor: any, methodName: any) {}

class A {
    @test
    public x() {}

    public y() {}
}

type DecoratedOnly<T> = {
    [P in keyof T]: T[P] extends /* Magic Happens */ ? T[P] : never;
};

let a: DecoratedOnly<A> = {} as any;
a.x(); // ok
a.y(); // never!

Run Code Online (Sandbox Code Playgroud)

是否可以推断类的装饰方法,以便DecoratedOnly泛型类型保持装饰x()方法不变并省略非装饰y()方法?

jca*_*alz 6

据我所知,答案可能是“不”。装饰器当前不会改变类型,因此类型系统不会注意到装饰和未装饰方法之间的差异。人们在这里要求类装饰器(而不是像您使用的方法装饰器)这样的东西......但这是一个有争议的问题。有些人强烈认为装饰器不应该被类型系统观察到,而另一些人则同样强烈地认为不然。在 JavaScript 中的装饰器最终确定之前,TypeScript 的维护者不太可能对其工作方式进行任何更改,因此我不期望这里立即得到任何解决方案。

\n\n
\n\n

但是,如果我们备份并尝试提出一个与应用这些装饰器具有相同效果的解决方案,同时跟踪文件系统中发生的情况,该怎么办?

\n\n

为了得到具体的工作,我将做test()一些事情:

\n\n
function test(\n  target: any,\n  propertyKey: string,\n  descriptor: PropertyDescriptor\n) {\n  console.log(\n    "decorated test on target",\n    target,\n    "propertyKey",\n    propertyKey,\n    "descriptor",\n    descriptor\n  );\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

当你A这样做时:

\n\n
class A {\n  @test\n  public x() {}\n\n  public y() {}\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

您会得到以下日志:decorated test on target Object { \xe2\x80\xa6 } propertyKey x descriptor Object { value: x(), writable: true, enumerable: false, configurable: true }

\n\n
\n\n

由于我们无法检测何时应用装饰器,因此如果我们@test根本不使用装饰样式,而是调用test属性描述符上的实际函数(这就是装饰器编译为的方法)会怎样?如果我们创建自己的 apply-instance-method-decorator 函数,我们可以使该函数既执行装饰跟踪在类型系统中装饰了哪些方法。像这样的东西:

\n\n
function decorateInstanceMethods<T, K extends Extract<keyof T, string>>(\n  ctor: new (...args: any) => T,\n  decorator: (\n    target: any,\n    propertyKey: string,\n    descriptor: PropertyDescriptor\n  ) => void,\n  ...methodsToDecorate: K[]\n): T & { decoratedMethods: K[] } {\n  methodsToDecorate.forEach(m =>\n    decorator(\n      ctor.prototype,\n      m,\n      Object.getOwnPropertyDescriptor(ctor.prototype, m)!\n    )\n  );\n  return Object.assign(ctor.prototype, {\n    decoratedMethods: methodsToDecorate\n  });\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

该函数可能隐藏在某个库中。以下是您如何制作A和装饰它test

\n\n
class A {\n  public x() {}\n  public y() {}\n}\n\nconst DecoratedAPrototype = decorateInstanceMethods(A, test, "x");\n
Run Code Online (Sandbox Code Playgroud)\n\n

最终记录的内容与之前相同:decorated test on target Object { \xe2\x80\xa6 } propertyKey x descriptor Object { value: x(), writable: true, enumerable: false, configurable: true }

\n\n

但现在DecoratedAPrototypeisA.prototype添加了一个decoratedMethods属性,其类型为Array<"x">,因此您可以执行以下操作:

\n\n
type DecoratedOnly<\n  T extends {\n    decoratedMethods: (keyof T)[];\n  }\n> = Pick<T, T["decoratedMethods"][number]>;\n\nconst a: DecoratedOnly<typeof DecoratedAPrototype> = new A();\na.x(); // okay\na.y(); // error, property "y" does not exist on DecoratedOnly<typeof DecoratedAPrototype>\n
Run Code Online (Sandbox Code Playgroud)\n\n

您可以看到该A类型仍然不知道哪些方法被修饰,但DecoratedAPrototype确实知道。这足以为您提供您正在寻找的行为(我使用过,Pick因此省略的属性只是不知道存在并且没有明确地never......我猜这不是超级重要)

\n\n

那对你有用吗?是的,它比仅仅使用装饰器要复杂一点,但这是我能得到的最接近你想要的东西。

\n\n

无论如何,希望有帮助。祝你好运!

\n\n

链接到代码

\n