Typescript for "type, or function returning type"

106 Views Asked by At

It's useful to allow consumers to provide configuration either as static data, or as a function returning that same schema of data.

Consider some example types:

type T1 = { a: string } | (() => { a: string });
type T2 = { b: boolean, c: number } | (() => { b: boolean, c: number });
type T3 = string[] | (() => string[]);

In general, these types look like:

type T<V> = V | (() => V);

How can I write a type which takes some value in the form of T<V>, and returns V?

I tried:

type ResolveMaybeFunction<T> = ReturnType<T> | T;

But this is no good because ReturnType<T> results in any, so ResolveMaybeFunction<T> always results in any.

Overall, I want the following results:

ResolveMaybeFunction<string | (() => string)>                       === string;
ResolveMaybeFunction<{ a: number[] } | (() => { a: number[] })>     === { a: number[] };
ResolveMaybeFunction<AnyArbitraryType | (() => AnyArbitraryType)>   === AnyArbitraryType;
2

There are 2 best solutions below

1
On BEST ANSWER

You need to use a Conditional Type to check if the given type is a function, and then use the return type of the function if it is.

type ResolveMaybeFunction<T> = T extends ((...args: any) => any) ? ReturnType<T> : T

This says if T is of type function with any number of arguments and has a return value, use the return type of the function. Otherwise, just use the original type

Playground Link

0
On

You can just disregard the function member from the union type with Exclude<MyUnion, MembersToExclude>, since the value itself and the return value of the function type should be the same.

type Test1 = Exclude<'a' | 'b', 'b'> // 'a'
type Test1 = Exclude<'a' | () => 'a', () => unknown> // 'a'

Which in your case might look like this:

type MaybeFunction<V> = V | (() => V);

type ResolveMaybeFunction<T extends MaybeFunction<unknown>> =
  Exclude<T, () => unknown>

type Test1 = ResolveMaybeFunction<123 | () => 123> // 123

See playground


Note that if the value type itself is a function this won't work.

type T4 = MaybeFunction<() => 'a'>;
type Test4 = ResolveMaybeFunction<T4>
//   ^? never

Resolving that is more complicated. But maybe that's not a problem that needs solving.