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)