As the title says, here are all the useful types that I'm using every day or create new types on top of them. I thought it might be handy for some people so I just share here and this will be updated moving forward.
More resources
- https://github.com/piotrwitek/utility-types/tree/master?tab=readme-ov-file#table-of-contents
- https://github.com/millsp/ts-toolbelt
Overloading
type Foo = (a: string) => string; type Bar = (a: number) => number; // it becomes overloading declare let fn: Foo & Bar; const x = fn(2); // nubmer const y = fn("hello"); // string
Strict union type
type A<T> = { isMulti: false; foo: string; onChange: (value: T) => any; }; type B<T> = { isMulti: true; foo: number; onChange: (value: T[]) => any; }; type C<T> = { foo: string; onChange: (value: T) => any; }; // NOTE: this one only works well in case of `strict` mode on // https://stackoverflow.com/questions/65805600/struggling-with-building-a-type-in-ts#answer-65805753 type UnionKeys<T> = T extends T ? keyof T : never; type StrictUnionHelper<T, TAll> = T extends any ? T & Partial<Record<Exclude<UnionKeys<TAll>, keyof T>, never>> : never; type StrictUnion<T> = StrictUnionHelper<T, T>; type Unions<T> = StrictUnion<A<T> | B<T> | C<T>>; type Options<T = any> = { options: T[]; } & Unions<T>; interface Option { value: string; } const a: Options<Option> = { options: [ { value: "abc" } ], // trick is here onChange: <T extends Option>(value: T) => { return value; } }; // test if (a.isMulti) { a.onChange(); } // unknown const b = { // ... } as Unions<Option>; if (b.isMulti) { b.foo; // number } else { b.foo; // string }
Loop through an tuple array type
type Foo = [ { id: number; items: [ } ] type ReduceItems<Arr extends ReadonlyArray<any>, Result extends any[] = []> = Arr extends [] ? Result : Arr extends [infer H] ? H extends {items: ReadonlyArray<MenuItem>} ? [...Result, ...H["items"]] : never : Arr extends readonly [infer H, ...infer Tail] ? Tail extends ReadonlyArray<any> ? H extends {items: ReadonlyArray<MenuItem>} ? ReduceItems<Tail, [...Result, ...H["items"]]> : never : never : never;
- Deep required
type DeepRequired<T> = { [K in keyof T]: Required<DeepRequired<T[K]>>; };
- Make some props become required:
type RequiredProps<T extends object, K extends keyof T = keyof T> = Omit<T, K> & Required<Pick<T, K>>;
- Make some props become optional
type Optional<T, K extends keyof T> = Omit<T, K> & Partial<T>;
Recursively partial type
type RecursivePartial<T> = { [P in keyof T]?: T[P] extends (infer U)[] ? RecursivePartial<U>[] : T[P] extends object | undefined ? RecursivePartial<T[P]> : T[P]; };
-
Array.prototype.map
returns as const tuple:
function map<T extends any[]>( // arr: T arr: readonly [...T] ): [...{ [I in keyof T]: `foo${T[I]}`}] { // return arr.map(elem => `foo${elem}`) as any; } const arr = ['x', 'y'] as const satisfies readonly ('x' | 'y' | 'z')[] const result = map(arr) // ['foox', 'fooy']
Keep array as const
with suggestion enabled:
type FooBar = "foo" | "bar"; // `foo` now has type as `readonly ["foo", "bar"]` const foo = ["foo", "bar"] as const satisfies readonly FooBar[];
Get all paths for all props:
type NestedProps<T extends object> = { [P in keyof T & (string | number)]: T[P] extends Date ? `${P}` : T[P] extends Record<string, unknown> ? `${P}` | `${P}.${NestedProps<T[P]>}` : `${P}`; }[keyof T & (string | number)];
Check an arbitrary property exits in an generic object:
function hasOwnProperty<X extends Record<string, any>, Y extends PropertyKey>( obj: X, prop: Y ): obj is X & Record<Y, any> { return prop in obj; }
Split string with space as smaller strings
type Parts<Path> = Path extends `${infer PartA} ${infer PartB}` ? PartA | Parts<PartB> : Path; // Test type Test0 = Parts<'a b c'> // 'a' | 'b' | 'c'
Is union type
type IsUnion<T, U extends T = T> = T extends unknown ? ([U] extends [T] ? false : true) : false;
Is never type
type IsNever<T> = [T] extends [never] ? true : false; // Test type Test0 = IsNever<never> // true; type Test0 = IsNever<string> // false;
Is type an any
?
type IsAny<T> = 0 extends 1 & T ? true : T; // Test type Test0 = IsAny<any> // true; type Test1 = IsAny<string> // string;
Is an empty object
type IsEmptyObject<T extends Record<PropertyKey, unknown>> = [keyof T] extends [never] ? true : false; // Test type Test0 = IsEmptyObject<{}> // true type Test1 = IsEmptyObject<{foo: string}> // false type Test2 = IsEmptyObject<{[K: string]: any}> // false
Known keys
type KnownKeys<T> = { [K in keyof T as string extends K ? never : number extends K ? never : symbol extends K ? never : K]: T[K]; } // Test type Test0 = KnownKeys< {foo: string; [K: string]: any} // index signature >; // {foo: string}
Flatten object keys
type FlattenObjectKeys< T extends Record<string, unknown>, Key = keyof T > = Key extends string ? T[Key] extends Record<string, unknown> ? `${Key}.${FlattenObjectKeys<T[Key]>}` : `${Key}` : never // Test type Test0 = FlattenObjectKeys<{foo: {bar: string}}> // foo | foo.bar
Deep exclude
type DeepExclude<T, U> = T extends U ? never : T extends object ? { [K in keyof T]: DeepExclude<T[K], U>; } : T; // Test type Test0 = DeepExclude< {foo: string, bar: number}, {foo: string} > // {bar: number}
- Omit never values
type OmitNever<T> = Omit< T, { [K in keyof T]-?: IsNever<T[K]> extends true ? K : never; }[keyof T] >; // Test type Test0 = OmitNever<{foo: string; bar: never}> // {foo: string}
Deep omit keys
type OmitKeysRecursively<T, K extends PropertyKey> = Omit< { [P in keyof T]: T[P] extends object ? OmitKeysRecursively<T[P], K> : T[P]; }, K >; // Test type Test0 = OmitKeysRecursively< {foo: string; bar: {baz: number, biz: boolean}}, 'foo' | 'baz' > // {foo: string; bar: {biz: boolean}}
- Pick keys which is a bit complicated than omit keys
// helper helps remove keys with `never` as value type OmitNever<T> = Omit< T, { [K in keyof T]-?: Pick<T, K> extends Partial<Record<K, undefined>> ? K : never; }[keyof T] >; type PickRecursively<T extends object, K extends PropertyKey> = OmitNever<{ [P in keyof T]: P extends K ? T[P] : T[P] extends infer O ? O extends object ? PickRecursively<O, K> : never : never; }> extends infer O ? { [P in keyof O]: O[P] } : never; // Test type TestP0 = PickRecursively< { foo: string; bar: { baz: string; bix: string } }, 'foo' | 'bar' >; // {foo: string; bar: {baz: string}}
A type of mapping
export enum Group { FOO = 'foo', BAR = 'bar', } interface Mapping { [Group.FOO]: { fooString: string; fooNumber: number }; [Group.BAR]: { barString: string; barDate: Date; notFoo: string }; } type Values<T> = T[keyof T] type ParamAsArray<T> = Values<{ [P in keyof T]: [P, T[P]] }> function method(...[p0, p1]: ParamAsArray<Mapping>) {...} // call the method as normal method(Group.FOO, {...});
An another example of checking others props type based on an particular prop:
type Values<T> = T[keyof T]; type Props = Values<{ [P in keyof JSX.IntrinsicElements]: { as: P } & { [K in keyof JSX.IntrinsicElements[P]]: JSX.IntrinsicElements[P][K]; }; }>; // Do not spread the props directly on parameter // like: `{as, ...rest}` // since TS is now not dynamic enough to update // `rest` with correct type based on `as` // but `rest` will contain all common props const BaseComponent: React.FC<Props> = (props) => { if (props.as === 'a') { const { as, ...others } = props; console.log(others.href) // ok } };
Top comments (0)