Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typescript: change function type so that it returns new value

Basically, I want something like this:

export type ReturnValueMapper<Func extends (...args: Args[] /* impossible */ ) => any, ReturnValue> = (...args: Args[]) => ReturnValue;

I'm almost sure that it's impossible, but I haven't found exact confirmation.


The use case is improving types for recompose's withStateHandlers, enabling defining state updaters like this:

interface StateUpdaters {
    update(field: string): void; // I don't want to specify Partial<State> here 
}
like image 509
Leonid Fenko Avatar asked Apr 24 '18 22:04

Leonid Fenko


People also ask

How do you change the return type of a function in TypeScript?

To define the return type for the function, we have to use the ':' symbol just after the parameter of the function and before the body of the function in TypeScript. The function body's return value should match with the function return type; otherwise, we will have a compile-time error in our code.

How do I return a value from a function in TypeScript?

The function's return type is string. Line function returns a string value to the caller. This is achieved by the return statement. The function greet() returns a string, which is stored in the variable msg.

What does () => void mean TypeScript?

Introduction to TypeScript void type The void type denotes the absence of having any type at all. It is a little like the opposite of the any type. Typically, you use the void type as the return type of functions that do not return a value.

How do I return different data types in TypeScript?

Use a union type to define a function with multiple return types in TypeScript, e.g. function getValue(num: number): string | number {} . The function must return a value that is represented in the union type, otherwise the type checker throws an error.


1 Answers

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:

type ReplaceReturnType<T extends (...a: any) => any, TNewReturn> = (...a: Parameters<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

Original


For a good solution you will need variadic types, but for now this answer provides a workable solution. (Posting it here as the type there is used as part of a solution to a different question).

The basic idea is that we will extract the parameter types and recompose the function signature with the new return type. There are several disadvantages to this approach:

  1. Parameter names are not preserved
  2. Optional parameters are not handled well
  3. Only works for a specific number of arguments (but more can be added as needed)

There may be other issues, but depending on your use-case this may be a good enough solution until the type system addresses this use case.

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

The issue when used with optional parameters is that the optional parameter becomes required (and is of type A | undefined):

type WithOptional = ReplaceReturnType<(n?: number)=> string, Promise<string>>;

let x!: WithOptional;
x(); //invalid
x(undefined);
x(1);
like image 126
Titian Cernicova-Dragomir Avatar answered Sep 30 '22 17:09

Titian Cernicova-Dragomir