Interesting TypeScript types that solve interesting TypeScript problems
Don't wanna be here? Send us removal request.
Text
Preventing Distributive Type Mapping in Conditionals
Conditional types can be useful for narrowing a type parameter. However, when the checked type is a pure type reference, TypeScript "recalculates" the type in all subsequent usage (both the true and false cases). This can be useful in some cases where different types in a single union apply to multiple conditionals.
However, this behavior of distributing a union type over a generic type can lead to some confusing behavior.
type Transform<T> = (x: T) => T; type StringTransform1<T> = T extends string ? Transform<T> : Transform<unknown>; type StringTransform2<T> = [T] extends [string] ? Transform<T> : Transform<unknown>; declare const transform1: StringTransform1<"a" | "b">; transform1("a"); declare const transform2: StringTransform2<"a" | "b">; transform2("a");
Due to the distribution in StringTransform1 it produces the type Transform | Transform. In TypeScript, parameters are contravariant which means that this computed union flattens to (x: never) => "a" | "b" which is not particularly useful.
To circumvent this behavior, the checked type in the conditional can be a type constructed from the reference. The simple example used here is a one-element tuple, that replaces both the check and extends types. The conditional check should function the same, but it now prevents TypeScript from narrowing the type reference in subsequent cases.
0 notes
Text
Disambiguating Tuples from Homogenous Arrays
type ElementType1<T extends any[]> = T[number]; type ArrayElement1 = ElementType1<number[]>; type TupleElement1 = ElementType1<[number, string]>; type ElementType2<T extends any[]> = number extends T["length"] ? { arrayOf: T[number] } : { tuple: T } type ArrayElement2 = ElementType2<number[]>; type TupleElement2 = ElementType2<[number, string]>;
When working with array types, it's a fairly common use case to get the type of the element. The simplest option is the first element type above, T[number] where TypeScript will index into the array type with number. This works for homogenous arrays easily, but for tuples it unions all of the possible element types together.
However, there may be cases where homogenous arrays and tuples should be treated differently. For tuples, TypeScript overrides the .length property with a literal indicating the number of elements. This can be used to disambiguate between the two, by checking if number extends T["length"].
0 notes
Text
Reverse Lookup for Discriminated Unions
interface Foo { kind: "foo"; } interface Bar { kind: "bar"; } type NarrowByKind<T extends { kind: string }> = { [K in T["kind"]]: T extends { kind: K } ? T : never; }; type ReverseLookup = NarrowByKind<Foo | Bar>;
Discriminants (kind: "literal") are incredibly useful to disambiguate between multiple options in a union type. However, there are some cases where it makes sense to do a reverse lookup from kind back to type.
1 note
·
View note