【问题标题】:Is modifying method's return type by decorator possible?是否可以通过装饰器修改方法的返回类型?
【发布时间】:2018-08-31 21:57:35
【问题描述】:

假设我有一个 A 装饰器,它被应用于返回字符串的方法。装饰器使用该字符串并最终返回B 类型(一个类)。

class B {
  constructor(text: string) { ... }

  method() {...}
}

class X {
  @A
  someMethod(): string {
    return 'lalala';
  }
}

装饰器

function A(target, property, descriptor) {
  const text: string = descriptor.value();

  descriptor.value = () => new B(text);
}

发生了什么?现在someMethod 返回一个B 对象而不是字符串。但我不能这样做:

class X {
  constructor() {
    this.someMethod().method();
  }

  @A
  someMethod(): string {
    return 'lala';
  }
}

为什么?因为定义中的someMethod 是字符串类型,但装饰器使它返回B 类型。我能否以某种方式让打字稿知道someMethod 实际上返回B,而不是string

【问题讨论】:

标签: typescript decorator


【解决方案1】:

3.0 解决方案见下文

装饰器不能改变类型的结构,没有任何类型的装饰器可以做到这一点(类、方法或参数装饰器)

使用 Typescript 2.8,您可以编写一个将另一个函数作为参数并执行返回类型更改的函数,但您会丢失参数名称、可选参数和多个签名等内容。您还应该小心,因为这样做会使someMethod 成为分配方法的字段,而不是类方法。因此,该字段必须在每个构造函数中分配,而不是一次分配给 prototype,这可能会影响性能。

class B<T> {
    constructor(public value: T) { }

    method() { return this.value; }
}
function A<T extends (...args: any[]) => any>(fn: T): ReplaceReturnType<T, B<ReturnType<T>>> {
    return function (this: any, ...args: any[]) {
        return new B<ReturnType<T>>(fn.apply(this, args));
    } as any;
}

class X {
    constructor() {
        this.someMethod().method();
    }
    other() {}
    someMethod = A(function (this: X): string {
        this.other(); // We can access other members because of the explicit this parameter
        return 'lala';
    });
}
type IsValidArg<T> = T extends object ? keyof T extends never ? false : true : true;
type ReplaceReturnType<T, TNewReturn> = T extends (a: infer A, b: infer B, c: infer C, d: infer D, e: infer E, f: infer F, g: infer G, h: infer H, i: infer I, j: infer J) => infer R ? (
    IsValidArg<J> extends true ? (a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J) => TNewReturn :
    IsValidArg<I> extends true ? (a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I) => TNewReturn :
    IsValidArg<H> extends true ? (a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H) => TNewReturn :
    IsValidArg<G> extends true ? (a: A, b: B, c: C, d: D, e: E, f: F, g: G) => TNewReturn :
    IsValidArg<F> extends true ? (a: A, b: B, c: C, d: D, e: E, f: F) => TNewReturn :
    IsValidArg<E> extends true ? (a: A, b: B, c: C, d: D, e: E) => TNewReturn :
    IsValidArg<D> extends true ? (a: A, b: B, c: C, d: D) => TNewReturn :
    IsValidArg<C> extends true ? (a: A, b: B, c: C) => TNewReturn :
    IsValidArg<B> extends true ? (a: A, b: B) => TNewReturn :
    IsValidArg<A> extends true ? (a: A) => TNewReturn :
    () => TNewReturn
) : never

编辑

自从回答了原始问题以来,打字稿已经改进了该问题的可能解决方案。添加Tuples in rest parameters and spread expressions 后,我们现在不需要为ReplaceReturnType 提供所有重载:

type ArgumentTypes<T> = T extends (... args: infer U ) => infer R ? U: never;
type ReplaceReturnType<T, TNewReturn> = (...a: ArgumentTypes<T>) => TNewReturn;

这不仅更短,而且解决了许多问题

  • 可选参数仍然是可选的
  • 参数名称被保留
  • 适用于任意数量的参数

示例:

type WithOptional = ReplaceReturnType<(n?: number)=> string, Promise<string>>;
let x!: WithOptional; // Typed as (n?: number) => Promise<string>
x() // Valid
x(1); //Ok

【讨论】:

  • 我认为这个解决方案使 method() 成为每个实例对象的属性,而不是原型方法,如果重要的话。
  • @jcalz 是的,那是 100%,想将其添加到答案中但忘记了,我将添加注释,10x :)
  • @jcalz 你看到这个了吗?,也许你有更好的解决方案...stackoverflow.com/questions/49427721/…
猜你喜欢
  • 2016-10-09
  • 2012-12-02
  • 1970-01-01
  • 2020-02-29
  • 2014-03-18
  • 2021-03-17
  • 1970-01-01
  • 2012-09-03
  • 2019-02-12
相关资源
最近更新 更多