I am studying the source code of the ts-toolbelt library.
And I often meet this expressionO extends unknown. In my opinion, it does not add any functionality.
And so I wondered, what is it for?
/**
* @hidden
*/
export type _UnionOf<O extends object> =
O[keyof O]
/**
* Transform an [[Object]] into an [[Union]]
* @param O to transform
* @returns [[Any]]
* @example
* ```ts
* ```
*/
export type UnionOf<O extends object> =
O extends unknown
? _UnionOf<O>
: never
For some reason, instead of exporting the _UnionOf type, it is preceded by the expression O extends unknown
Presumably the library authors decided that UnionOf<A | B | C> should produce the same result as UnionOf<A> | UnionOf<B> | UnionOf<C>, and the definition of _UnionOf did not do that. The O extends unknown ? ... : never check, which deceptively looks like it does nothing, causes that to happen.
Expressions that look like they do nothing but actually distribute across unions, when T is a generic type parameter:
T extends unknown ? ... : neverT extends any ? ... : neverT extends T ? ... : neverIf T is a type parameter, as in the generic function function foo<T>(/*...*/): void, the generic type alias type Foo<T> = /*...*/, or the generic interface interface Foo<T> {/*...*/}, then a type of the form T extends XXX ? YYY : ZZZ is a distributive conditional type.
It distributes the conditional check across unions in T. When T is specified with some specific type, the compiler splits that type up into its union members, evaluates the check for each such member, and then joins the results back into a new union. So if F<T> distributes across unions, then F<A | B | C> will be the same as F<A> | F<B> | F<C>.
Not all type functions are distributive across unions. For example, the keyof operator does not turn unions of inputs into unions of outputs:
type KeyofA = keyof {a: string}; // "a"
type KeyofB = keyof {b: number}; // "b"
type KeyofAorB = keyof ({ a: string } | { b: number }); // never
Similarly, _UnionOf is not distributive across unions (related to the fact that it uses keyof in its definition):
type Oops = _UnionOf<{ a: string } | { b: number }>
// never
If you have a type function which is not distributive in unions and you want it to be, you can wrap it in a distributive conditional type:
type DistribKeyof<T> = T extends unknown ? keyof T : never;
type UnionKeyofAorB = DistribKeyof<{ a: string } | { b: number }>; // "a" | "b"
And therefore UnionOf is distributive across unions:
type Correct = UnionOf<{ a: string } | { b: number }>
// string | number
Playground link to code
O extends unknown ? _UnionOf<O> : never is a conditional type; here it is used as a distributive conditional type. Without using a conditional type, TypeScript will narrow the type to one that applies to all possible union values (often never). With distributive conditional types, it will apply to each potential union value. Though the conditional aspect of distributive conditional types could be used to filter the potential union values, here that is unnecessary, so you'll frequently see values like T extends any (as in the handbook) or T extends unknown here.
As for the difference between those, dragomirtitian describes it in the Typescript gitter.im:
anysometimes deletes information about the type parameter. There was a bug around this I filed myself microsoft/TypeScript#30569. It should work ok now, but I would still recommend one of the other two ways [ed:T extends unknownorT extends T]. Any extends clause in a conditional type introduces information aboutT,extends anycan confuse the compiler aboutT
See also:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With