Is modifying method's return type by decorator possible?

后端 未结 1 1293
不知归路
不知归路 2020-12-21 18:26

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

相关标签:
1条回答
  • 2020-12-21 18:44

    See below for 3.0 solution

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

    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
    

    Edit

    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

    • Optional parameters remain optional
    • Argument names are preserved
    • Works for any number of arguments

    Sample:

    type WithOptional = ReplaceReturnType<(n?: number)=> string, Promise<string>>;
    let x!: WithOptional; // Typed as (n?: number) => Promise<string>
    x() // Valid
    x(1); //Ok
    
    0 讨论(0)
提交回复
热议问题