工具类型实现
内置类型
Partial<T>
// example
interface Todo {
title: string;
description: string;
}
function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) {
return { ...todo, ...fieldsToUpdate };
}
const todo1 = {
title: "organize desk",
description: "clear clutter"
};
const todo2 = updateTodo(todo1, {
description: "throw out trash"
});
// implement
type Partial<T> = {
[P in keyof T]?: T[P]
}
Required<T>
type Required<T> = {
[P in keyof T]-?: T[P]
}
Readonly<T>
type Readonly<T> = {
readonly [P in keyof T]: T[P]
}
Pick<T, K>
// example
interface Todo {
title: string;
description: string;
completed: boolean;
}
type TodoPreview = Pick<Todo, "title" | "completed">;
const todo: TodoPreview = {
title: "Clean room",
completed: false
};
// implement
type Pick<T, K extends keyof T> = {
[P in K]: T[P]
}
Omit<T, K>
// example
interface Todo {
title: string;
description: string;
completed: boolean;
}
type TodoPreview = Omit<Todo, "description">;
const todo: TodoPreview = {
title: "Clean room",
completed: false
};
// implement
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
// or
type Omit<T, K> = {
[P in Exclude<keyof T, K>]: T[P]
}
Extract<T, U>
type Extract<T, K> = T extends K ? T : never
Exclude<T, U>
// example
type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
type T1 = Exclude<"a" | "b" | "c", "a" | "b">; // "c"
type T2 = Exclude<string | number | (() => void), Function>; // string | number
// implement
// extends搭配 ?: 运算符使用
type Exclude<T, U> = T extends U ? never : T;
// 相当于: type A = 'a'
type A = Exclude<'x' | 'a', 'x' | 'y' | 'z'>
Record<K, T>
type Record<K extends keyof any, T> = {
[P in K]: T
}
NonNullable<T>
type NonNullable<T extends keyof any> = T extends null | undefined ? never : T
Parameters<T>
// infer 关键字
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
ReturnType<T>
// infer 关键字
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : never
ConstructorParameters<T>
type ConstructorParameters<T extends new (...args: any) => any> = T extends new (...args: infer P) => any ? P : never;
InstanceType<T>
type InstanceType<T extends new (...args: any) => any> = T extends new (...args: any) => infer R ? R : any;
Awaited<T>
type MyAwaited<T> = T extends Promise<infer T> ? MyAwaited<T> : T;
其他
Uppercase
,全体大写Lowercase
,全体小些Capitalize
,首字母大写Uncapitalize
,首字母小写
Equal
非内置工具类型,用于比较两个类型完全相等,因为其高泛用性和实现的特殊性所以特别拎出来讨论。社区主流的实现如下:
export type Equal<X, Y> =
(
(<T>() => (T extends X ? 1 : 2)) extends
(<T>() => (T extends Y ? 1 : 2))
)
? true
: false;
Type Challenge
开始做题
简单题
Tuple To Object
问题的本质是元祖如何转化为联合类型
type TupleToObject<T extends readonly any[]> = {
[k in T[number]]: k
}
First Of Array
type First<T extends any[]> = T extends [infer F, ...infer R] ? F : never;
Length Of Tuple
需要注意的是需要用readonly any[]
,否则元祖会走到never
分支
type Length<T> = T extends readonly any[] ? T['length'] : never;
Concat
type Concat<T extends readonly any[], U extends readonly any[]> = [...T, ...U]
Include
参考,需要借助额外实现的
Equal
类型
// 迭代写法
type IterativeIncludes<T extends readonly any[], U> = {
[K in keyof T]: Equal<T[K], U>;
}[number] extends false
? false
: true;
// 递归写法
type RecursiveIncludes<T extends readonly any[], U> = T extends [
infer F,
...infer R
]
? Equal<F, U> extends true
? true
: RecursiveIncludes<R, U>
: false;
中等题
Readonly2
type MyReadonly2<T, K extends keyof T = keyof T> = {
[k in Exclude<keyof T, K>]: T[k]
} & {
readonly [k in K]: T[k]
}
Deep Readonly
type DeepReadonly<T> = {
readonly [K in keyof T]: keyof T[K] extends never ? T[K] : DeepReadonly<T[K]>
}
Tuple To Union
type TupleToUnion<T extends readonly any[]> = T[number]
Chainable Options
type Chainable<T = {}> = {
option<K extends string, V extends any>(key: K, value: Exclude<V, K extends keyof T ? T[K] : never>): Chainable<{
[k in (keyof T) | K]: k extends keyof T ? K extends keyof T ? V : T[k] : V
}>
get(): T
}
注意点
option
的返回值中需要进行字段的覆盖,不用单纯用&
,除了这里写的方法也可以先Omit
再&
- 需要对
option
的参数value
的类型V
进行约束,比如在某些情况下V
的类型不能是string
扩展阅读
Exclude<T, string> = 'hi',一元方程式求解,自动推导出T的类型为'hi',arg为never
const fn = <T extends any>(arg: Exclude<T, string>) => arg;
fn('hi') // const fn: <"hi">(arg: never) => never
Promise.all
type Util<T extends readonly any[]> = T extends readonly [infer F, ...infer L] ? [Awaited<F>, ...Util<L>] : []
declare function PromiseAll<T extends any[]>(values: readonly [...T]): Promise<Util<typeof values>>
注意点
- 这里通过
extends [infer F]
来递归获取元组的参数,但是数组不会跑到这个分支,所以我们需要一种手段把数组转化为等效的元组,通过values: readonly [...T]
实现
Type Lookup
type LookUp<U, T> = U extends {
type: T
} ? U : never;
Trim Left
type TrimLeft<S extends string> = S extends `${' ' | '\n' | '\t'}${infer R}` ? TrimLeft<R> : S;
Capitalize
type MyCapitalize<S extends string> = S extends `${infer T}${infer R}` ? `${Uppercase<T>}${R}`: S
Replace
type Replace<S extends string, From extends string, To extends string> = S extends `${infer L}${Exclude<From, ''>}${infer R}` ? `${L}${To}${R}` : S;
ReplaceAll
type ReplaceAll<S extends string, From extends string, To extends string> = S extends `${infer L}${Exclude<From, ''>}${infer R}` ? `${L}${To}${ReplaceAll<R, From, To>}` : S
Amend Argument
type AppendArgument<Fn extends (...args: any[]) => any, A> = (...args: [...Parameters<Fn>, A]) => ReturnType<Fn>
Permutation
type Permutation<T, K = T> = [T] extends [never] ? [] : T extends any ? [T, ...Permutation<Exclude<K, T>>] : never;
注意点
- 触发类型分发后,为了仍然能够获取完整的联合类型,我们额外声明了一个类型参数
K = T
Length Of String
type LengthOfString<S extends string, Len extends any[] = []> = S extends `${infer L}${infer R}` ? LengthOfString<R, [...Len, L]> : Len['length']
思路:字符串转元组
Flatten
type Flatten<T> = T extends any[] ? T extends [infer L, ...infer R] ? [...Flatten<L>, ...Flatten<R>] : [] : [T]
Amend To Object
type AppendToObject<T, U extends keyof any, V> = {
[K in (keyof T | U)]: K extends keyof T ? T[K] : V
}
Absolute
type Absolute<T extends number | string | bigint> = T extends string ? T extends `-${infer N}` ? N : T: Absolute<`${T}`>
思路:数字转字符串
String To Union
type StringToUnion<T extends string> = T extends `${infer L}${infer R}` ? L | StringToUnion<R>: never;
Merge
type Merge<F, S> = {
[K in keyof F | keyof S]: K extends keyof S ? S[K] : K extends keyof F ? F[K] : never;
}
Diff
type Diff<T, U> = Pick<{
[K in keyof T | keyof U]: K extends keyof T ? T[K] : K extends keyof U ? U[K] : never;
}, Exclude<keyof T | keyof U, keyof T & keyof U>>
IsNever
type IsNever<T> = [T] extends [never] ? true : false;
// 以下是错误答案
type IsNever2<T> = T extends never ? true : false
type Test = IsNever2<never> // never
注意点:
never
不能触发类型分发,否则结果是never
。
isUnion
type IsUnion<T, Copy = T> = [T] extends [never] ? false : T extends Copy ? [Copy] extends [T] ? false : true : never
思路:先对T
进行类型分发,然后再用T
的完整拷贝U
和T
进行比较来判断是否为联合类型
Remove Index Signature
type RemoveIndexSignature<T> = {
[K in keyof T as string extends K ? never : number extends K ? never : symbol extends K ? never : K]: T[K]
}
思路
- 问题的本质是对象的过滤。我们通过
as
子句的特性来实现该功能,当as
成never
后就会自动过滤该键
DropChar
type DropChar<S, C> = S extends `${infer L}${infer R}` ? L extends C ? DropChar<R, C> : `${L}${DropChar<R, C>}` : S;
MinusOne
TODO
PickByType
type PickByType<T, U> = {
[K in keyof T as T[K] extends U ? K : never]: T[K]
}
StartsWith
type StartsWith<T extends string, U extends string> = T extends `${U}${infer R}` ? true : false;
type EndsWith<T extends string, U extends string> = T extends `${infer L}${U}` ? L : false;
PartialByKeys
type Merge<T> = {
[K in keyof T]: T[K]
}
type PartialByKeys<T, K extends keyof any = keyof T> = Merge<Omit<T, K> & Partial<Pick<T, K & keyof T>>>
注意点
- 通过
Merge
实现{ name: string } & { age: number}
到{ name: string; age: number}
的转化 - 此处的
T
类型为string | number | symbol
,为了让它满足keyof T
有两种思路,一是通过T extends keyof T ? T : never
,一种是这里的T & keyof T
ObjectEntries
type ObjectEntries<T> = {
[K in keyof T]-?: [K, T[K] extends infer R | undefined ? R : T[K]]
}[keyof T]
Tuple to Nested Object
type TupleToNestedObject<T, U> = T extends [infer L, ...infer R] ? {
[K in L & keyof any]: TupleToNestedObject<R, U>
} : U;
Reverse
type Reverse<T, Result extends readonly any[] = []> = T extends [infer L, ...infer R] ? Reverse<R, [L, ...Result]> : Result;
Flip Arguments
type FlipArguments<T extends (...args: any[]) => any> = T extends (...args: infer P) => infer R ? (...args: Reverse<P>) => R : never;
BEM style string
type BEM<B extends string, E extends string[], M extends string[]> = `${B}${E extends [] ? '' : E[number] extends infer T ? `__${T & string}`: never}${M extends [] ? '' : M[number] extends infer R ? `--${R & string}`: never}`
InorderTraversal
type InorderTraversal<T extends TreeNode | null> = [T] extends [TreeNode] ? (
[
...InorderTraversal<T['left']>,
T['val'],
...InorderTraversal<T['right']>,
]
) : []
Filp
type Flip<T> = {
[K in keyof T as `${T[K] & (number | string | boolean) }` ]: K
}