在打字稿类型中区分装饰类方法 [英] Distinguishing decorated class methods in a typescript type

查看:23
本文介绍了在打字稿类型中区分装饰类方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想创建一个泛型类型,它只从类定义中选择装饰方法.

I want to create a generic type which picks only decorated methods from a class definition.

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!

是否可以推断类的装饰方法,所以 DecoratedOnly 泛型类型保持装饰的 x() 方法原样并省略未装饰的 y() 方法?

Is it possible to infer decorated methods of a class, so DecoratedOnly generic type keeps decorated x() method as is and omits non-decorated y() method?

推荐答案

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

As far as I can see the answer is probably "no". Decorators don't currently mutate types, so the type system won't notice a difference between decorated and undecorated methods. People have asked for something like this for class decorators (as opposed to a method decorator like you're using), here... but it's a controversial issue. Some people feel very strongly that decorators should be unobservable by the type system, whereas others feel just as strongly otherwise. And until decorators in JavaScript become finalized, the maintainers of TypeScript are not likely to make any changes to the way they work, so I wouldn't expect any immediate resolution here.

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

But what if we back up and try to come up with a solution that has the same effect as applying those decorators while keeping track of what's happening in the file system?

为了得到一些具体的东西,我要让 test() 做一些事情:

To get something concrete to work with, I'm going to make test() do something:

function test(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  console.log(
    "decorated test on target",
    target,
    "propertyKey",
    propertyKey,
    "descriptor",
    descriptor
  );
}

当你像这样制作 A 时:

And when you make A like this:

class A {
  @test
  public x() {}

  public y() {}
}

你得到以下日志:decorated test on target Object { ... } propertyKey x descriptor Object { value: x(), writable: true, enumerable: false, configure: true }

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

Since we can't detect when decorators are applied, what if we didn't use the @test decoration style at all, but instead called the actual test function on the property descriptor, which is what method decorators are compiled to anyway? If we make our own apply-instance-method-decorator function, we can make that function both do the decoration and keep track of which methods were decorated in the type system. Something like this:

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

该函数可能隐藏在某个库中.下面是你如何制作 A 并用 test 装饰它:

That function could be hidden away in a library somewhere. And here's how you'd make A and decorate it with test:

class A {
  public x() {}
  public y() {}
}

const DecoratedAPrototype = decorateInstanceMethods(A, test, "x");

这最终记录了与之前相同的内容:decorated test on target Object { ... } propertyKey x descriptor Object { value: x(), writable: true, enumerable: false, configure: true }

That ends up logging the same thing as before: decorated test on target Object { … } propertyKey x descriptor Object { value: x(), writable: true, enumerable: false, configurable: true }

但是现在,DecoratedAPrototypeA.prototype 并添加了 decoratedMethods 属性,其类型为 Array<"x">;,所以你可以这样做:

But now, DecoratedAPrototype is A.prototype with an added decoratedMethods property, whose type is Array<"x">, so you can do this:

type DecoratedOnly<
  T extends {
    decoratedMethods: (keyof T)[];
  }
> = Pick<T, T["decoratedMethods"][number]>;

const a: DecoratedOnly<typeof DecoratedAPrototype> = new A();
a.x(); // okay
a.y(); // error, property "y" does not exist on DecoratedOnly<typeof DecoratedAPrototype>

您可以看到 A 类型仍然不知道装饰了哪些方法,但是 DecoratedAPrototype 知道.这足以为您提供您正在寻找的行为(我使用了 Pick 所以省略的属性只是未知的存在,而不是明确的never...我猜这不是非常重要)

You can see that the A type still doesn't know anything about which methods were decorated, but DecoratedAPrototype does. And that is enough to give you the behavior you're looking for (I used Pick so the omitted properties are just not-known-to-exist and not explicitly never... it's not super important I guess)

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

Does that work for you? Yes, it's a bit more complicated than just using decorators, but it's the closest I could get to what you want.

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

Anyway, hope that helps. Good luck!

链接到代码

这篇关于在打字稿类型中区分装饰类方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆