This page contains various helper types and useful constructs.
Note that in general, advanced types like this improve DX by only a bit, and are not worth the decrease in maintainability. Generally these should only be used in libraries,
These are libraries that include many utility types, complementing the ones built into TypeScript.
AnyT
interface MyGeneric<T> { foo: T; }
type AnyMyGeneric = MyGeneric<unknown>;
type MyConditional<T extends string | number> = T extends string ? "string" | "number";
type AnyMyConditional = MyConditional<any>;
interface MyInvariantGeneric<T> { foo: T; bar(t: T): void; }
type AnyMyInvariantGeneric = MyInvariantGeneric<any>;
This is a type AnyMyGeneric
that’s defined so that any MyGeneric<T>
extends it.
Usually this can be done by making the generic parameter default to unknown
:
interface MyGeneric<T = unknown> {
foo: T;
}
But it may be useful to seperate it out for a number of reasons:
any
)
type TypeMap = {
foo: Foo;
bar: Bar;
};
TODO: show use cases
NoInfer<T>
type NoInfer<T> = [T][T extends unknown ? 0 : never];
This type prevents TypeScript from using that location to infer T
in a generic.
declare function doInferParam<T>(x: T): T;
declare function noInferParam<T>(x: NoInfer<T>): T;
const x1: boolean = doInferParam(1);
// ^^ error here - T is `number`
const x2: boolean = noInferParam(1);
// error here - T is `boolean` ^
const x3 = noInferParam(1);
// ^? - const x3: unknown
// Note that this doesn't error because TypeScript defaults
// any parameter it can't infer to `unknown`, which typechecks fine in this case.
declare function doInferObject<T>(x: { a: T; b: T }): T;
declare function noInferObject<T>(x: { a: NoInfer<T>; b: T }): T;
const x4 = doInferObject({ a: "1", b: 2 });
// ^? - const x4: string ^ type error here, `T` is inferred from `a`
const x5 = noInferObject({ a: "1", b: 2 });
// ^? - const x5: number
// ^ type error here, `T` is inferred from `b`
UnionToIntersection<T>
(by @jcalz: SO) Open in Playground
type UnionToIntersection<U> = (U extends U ? (u: U) => 0 : never) extends (
i: infer I
) => 0
? Extract<I, U>
: never;
This type converts unions (T | U | V
) into intersections (T & U & V
).
IsEqual<T, U>
(by @mattmccutchen: ic27024) Open in Playground
type IsEqual<T, U> = (<V>() => V extends T ? 1 : 0) extends <V>() => V extends U
? 1
: 0
? true
: false;
This type checks equality of types using the compiler’s notion of equality. Some differences between this, and types that extends
each other, are shown below.
type IsMututallyAssignable<T, U> = [T, U] extends [U, T] ? true : false;
type Compare<T, U> = [IsEqual<T, U>, IsMututallyAssignable<T, U>];
enum E {
a = 0,
b = 1,
}
type T1 = Compare<0, 0>;
// ^? - type T1 = [true, true]
type T2 = Compare<{ a: 0 } & { b: 1 }, { a: 0; b: 1 }>;
// ^? - type T2 = [false, true]
type T3 = Compare<any, { a: 0 }>;
// ^? - type T3 = [false, true]
type T4 = Compare<E, 0 | 1>;
// ^? - type T4 = [false, true]
type T5 = Compare<
((a: 0 | 1) => 0) & ((a: 1 | 2) => 0),
{ (a: 0 | 1): 0; (a: 1 | 2): 0 }
>;
// ^? - type T5 = [false, true]
type T6 = Compare<(a: string) => void, (a: string) => unknown>;
// ^? - type T6 = [false, true]
Narrow<T>
(by @tjjfvi) Open in Playground
type _Narrow<T, U> = [U] extends [T] ? U : Extract<T, U>;
type Narrow<T = unknown> =
| _Narrow<T, 0 | (number & {})>
| _Narrow<T, 0n | (bigint & {})>
| _Narrow<T, "" | (string & {})>
| _Narrow<T, boolean>
| _Narrow<T, symbol>
| _Narrow<T, []>
| _Narrow<T, { [_: PropertyKey]: Narrow }>
| (T extends object ? { [K in keyof T]: Narrow<T[K]> } : never)
| Extract<{} | null | undefined, T>;
This type is used to narrow the type of an expression to as exact of a type as possible.
It can be used either as a generic constraint, or in a satisfies
clause.
type T1 = number[];
type T2 = { foo: string, bar: string };
type T3 = { foo: string[], bar: Record<string, number>, baz: boolean };
let s0 = { a: 1, b: true, c: 'foobar', d: { a: 2, b: [1, 'a', { a: 1 }] } } satisfies Narrow;
// ^? - let s0: { a: 1, b: true, c: "foobar", d: { a: 2, b: [1, "a", { a: 1 }] } }
let s1 = [1, 2, 3, 4] satisfies Narrow<T1>;
// ^? - let s1: [1, 2, 3, 4]
let s2 = { foo: 'a', bar: 'b' } satisfies Narrow<T2>;
// ^? - let s2: { foo: "a", bar: "b" }
let s3 = { foo: ['a', 'b', 'c'], bar: { a: 1, b: 2, c: 3 }, baz: false } satisfies Narrow<T3>;
// ^? - let s3: { foo: ["a", "b", "c"], bar: { a: 1, b: 2, c: 3 }, baz: false }
const narrow = <T,>() => <U extends Narrow<T>>(x: U) => x;
const narrowT3 = <U extends Narrow<T3>>(x: U) => x;
let f0 = narrow()({ a: 1, b: true, c: 'foobar', d: { a: 2, b: [1, 'a', { a: 1 }] } });
// ^? - let f0: { a: 1, b: true, c: "foobar", d: { a: 2, b: [1, "a", { a: 1 }] } }
let f1 = narrow<T1>()([1, 2, 3, 4]);
// ^? - let f1: [1, 2, 3, 4]
let f2 = narrow<T2>()({ foo: 'a', bar: 'b' });
// ^? - let f2: { foo: "a", bar: "b" }
let f3 = narrowT3({ foo: ['a', 'b', 'c'], bar: { a: 1, b: 2, c: 3 }, baz: false });
// ^? - let f3: { foo: ["a", "b", "c"], bar: { a: 1, b: 2, c: 3 }, baz: false }
declare const x: unknown;
let s4 = x satisfies Narrow
// ^? - let s4: unknown
let f4 = narrow()(x)
// ^? - let f4: unknown