Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
899 views
in Technique[技术] by (71.8m points)

typescript - Is modifying method's return type by decorator possible?

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';
  }
}

decorator

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

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

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';
  }
}

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?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...