装饰器可以修改方法的返回类型吗? [英] Is modifying method's return type by decorator possible?

查看:493
本文介绍了装饰器可以修改方法的返回类型吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

让我们想象一下,我有一个 A 装饰器,该装饰器将应用于返回字符串的方法。装饰器使用该字符串,最后返回 B 类型(一个类)。

Let's imagine that I have an A decorator that is being applied on a method that returns a string. The decorator uses that string and in the end returns B type (a class).

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 对象,而不是字符串。但是我不能做这样的事情:

What happened? Now someMethod returns a B object instead of string. But I can't do something like this:

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

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

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

Why? Because someMethod from definition is of string type but the decorator makes it return B type. Can I in some way make the typescript know that someMethod in fact returns B, not string?

推荐答案

请参见下面的3.0解决方案

装饰器不能更改类型的结构,任何类型的装饰器都不能做到这一点(类,方法或参数装饰器)

A decorator can't change the structure of the type, no type of decorator can do this (class, method or parameter decorators)

使用Typescript 2.8,您可以编写一个函数,该函数将另一个函数作为参数并执行返回类型的更改,但是您将丢失诸如参数名称,可选参数和多个签名之类的内容。另外,您还应注意,因为这种方式使 someMethod 成为分配了方法而不是类方法的字段。因此,必须在每个构造函数中分配该字段,而不是一次将其分配给原型,这可能会对性能产生影响。

Using Typescript 2.8, you can write a function that will take another function as a parameter and perform a change of the return type, but you will loose things like parameter names, optional parameters and multiple signatures. Also you should take care, as this way of doing this makes someMethod a field that is assigned a method, instead of a class method. So the field will have to be assigned in each constructor instead of being assigned to the prototype once, this may have performance implications.

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

编辑

自从回答了原始问题以来,打字稿已经改善了此问题的可能解决方案。通过添加在剩余参数和扩展表达式中加倍,我们现在不需要具有 ReplaceReturnType 的所有重载:

Since the original question was answered typescript has improved the possible solution to this problem. With the addition of Tuples in rest parameters and spread expressions we now don't need to have all the overloads for ReplaceReturnType:

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

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

Not only is this shorter but it solves a number of problems


  • 可选参数保持可选

  • 保留参数名称

  • 可用于任意数量的参数

示例:

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

这篇关于装饰器可以修改方法的返回类型吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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