类型体操(Type Gymnastics)是指使用 TypeScript 类型系统进行复杂的类型推导和转换。掌握这些技巧可以创建更精确、更强大的类型。
类型编程基础#
类型编程的核心工具:
// TypeScript 5.x
// 1. 条件类型 - 类型级别的 if-elsetype IsString<T> = T extends string ? true : false
// 2. 映射类型 - 类型级别的 for-intype Readonly<T> = { readonly [K in keyof T]: T[K] }
// 3. 递归类型 - 类型级别的递归type DeepReadonly<T> = { readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K]}
// 4. 模板字面量 - 类型级别的字符串操作type EventName<T extends string> = `on${Capitalize<T>}`
// 5. infer - 类型级别的模式匹配type UnwrapPromise<T> = T extends Promise<infer U> ? U : T数组操作#
获取数组长度#
type Length<T extends readonly unknown[]> = T['length']
type L1 = Length<[1, 2, 3]> // 3type L2 = Length<[]> // 0获取头部和尾部#
type Head<T extends unknown[]> = T extends [infer H, ...unknown[]] ? H : never
type Tail<T extends unknown[]> = T extends [unknown, ...infer R] ? R : never
type Last<T extends unknown[]> = T extends [...unknown[], infer L] ? L : never
type H = Head<[1, 2, 3]> // 1type T = Tail<[1, 2, 3]> // [2, 3]type L = Last<[1, 2, 3]> // 3数组反转#
type Reverse<T extends unknown[]> = T extends [infer First, ...infer Rest] ? [...Reverse<Rest>, First] : []
type R = Reverse<[1, 2, 3]> // [3, 2, 1]数组扁平化#
type Flatten<T extends unknown[]> = T extends [infer First, ...infer Rest] ? First extends unknown[] ? [...Flatten<First>, ...Flatten<Rest>] : [First, ...Flatten<Rest>] : []
type F = Flatten<[1, [2, [3, 4]], 5]>// [1, 2, 3, 4, 5]数组去重#
type Includes<T extends unknown[], U> = T extends [infer First, ...infer Rest] ? Equal<First, U> extends true ? true : Includes<Rest, U> : false
type Unique<T extends unknown[], Result extends unknown[] = []> = T extends [ infer First, ...infer Rest,] ? Includes<Result, First> extends true ? Unique<Rest, Result> : Unique<Rest, [...Result, First]> : Result
// 辅助类型type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2 ? true : false
type U = Unique<[1, 2, 1, 3, 2]>// [1, 2, 3]对象操作#
深度必需#
type DeepRequired<T> = T extends object ? { [K in keyof T]-?: DeepRequired<T[K]> } : T
interface User { name?: string address?: { city?: string street?: string }}
type RequiredUser = DeepRequired<User>// 所有属性都变为必需深度可选#
type DeepPartial<T> = T extends object ? { [K in keyof T]?: DeepPartial<T[K]> } : T对象路径类型#
type Path<T, K extends keyof T = keyof T> = K extends string | number ? T[K] extends object ? K | `${K}.${Path<T[K]>}` : K : never
interface Config { database: { host: string port: number } server: { port: number }}
type ConfigPath = Path<Config>// 'database' | 'server' | 'database.host' | 'database.port' | 'server.port'路径取值类型#
type PathValue<T, P extends string> = P extends `${infer K}.${infer Rest}` ? K extends keyof T ? PathValue<T[K], Rest> : never : P extends keyof T ? T[P] : never
type DbHost = PathValue<Config, 'database.host'> // stringtype ServerPort = PathValue<Config, 'server.port'> // number字符串操作#
驼峰转换#
type CamelCase<S extends string> = S extends `${infer P}_${infer Q}` ? `${P}${Capitalize<CamelCase<Q>>}` : S extends `${infer P}-${infer Q}` ? `${P}${Capitalize<CamelCase<Q>>}` : S
type C1 = CamelCase<'hello_world'> // 'helloWorld'type C2 = CamelCase<'foo-bar-baz'> // 'fooBarBaz'蛇形转换#
type SnakeCase<S extends string> = S extends `${infer C}${infer Rest}` ? C extends Uppercase<C> ? C extends Lowercase<C> ? `${C}${SnakeCase<Rest>}` : `_${Lowercase<C>}${SnakeCase<Rest>}` : `${C}${SnakeCase<Rest>}` : S
type S1 = SnakeCase<'helloWorld'> // 'hello_world'type S2 = SnakeCase<'fooBarBaz'> // 'foo_bar_baz'字符串替换#
type Replace< S extends string, From extends string, To extends string,> = From extends '' ? S : S extends `${infer Before}${From}${infer After}` ? `${Before}${To}${After}` : S
type ReplaceAll< S extends string, From extends string, To extends string,> = From extends '' ? S : S extends `${infer Before}${From}${infer After}` ? ReplaceAll<`${Before}${To}${After}`, From, To> : S
type R1 = Replace<'hello world', ' ', '-'> // 'hello-world'type R2 = ReplaceAll<'a.b.c', '.', '/'> // 'a/b/c'去除空格#
type TrimLeft<S extends string> = S extends | ` ${infer Rest}` | `\n${infer Rest}` | `\t${infer Rest}` ? TrimLeft<Rest> : S
type TrimRight<S extends string> = S extends | `${infer Rest} ` | `${infer Rest}\n` | `${infer Rest}\t` ? TrimRight<Rest> : S
type Trim<S extends string> = TrimLeft<TrimRight<S>>
type T = Trim<' hello '> // 'hello'函数操作#
柯里化类型#
type Curry<F> = F extends (...args: infer A) => infer R ? A extends [infer First, ...infer Rest] ? Rest extends [] ? (arg: First) => R : (arg: First) => Curry<(...args: Rest) => R> : () => R : never
type Add = (a: number, b: number, c: number) => numbertype CurriedAdd = Curry<Add>// (arg: number) => (arg: number) => (arg: number) => number函数重载提取#
// 获取最后一个重载的类型type OverloadedReturnType<T> = T extends { (...args: any[]): infer R (...args: any[]): any} ? R : T extends (...args: any[]) => infer R ? R : never数学运算#
TypeScript 类型系统可以进行有限的数学运算:
计数#
type BuildTuple< L extends number, T extends unknown[] = [],> = T['length'] extends L ? T : BuildTuple<L, [...T, unknown]>
type Add<A extends number, B extends number> = [ ...BuildTuple<A>, ...BuildTuple<B>,]['length']
type Sum = Add<3, 4> // 7减法#
type Subtract<A extends number, B extends number> = BuildTuple<A> extends [...BuildTuple<B>, ...infer Rest] ? Rest['length'] : never
type Diff = Subtract<10, 3> // 7比较#
type GreaterThan< A extends number, B extends number, Acc extends unknown[] = [],> = A extends B ? false : Acc['length'] extends A ? false : Acc['length'] extends B ? true : GreaterThan<A, B, [...Acc, unknown]>
type GT1 = GreaterThan<5, 3> // truetype GT2 = GreaterThan<3, 5> // false实用类型#
必需属性#
type RequiredKeys<T> = { [K in keyof T]-?: {} extends Pick<T, K> ? never : K}[keyof T]
type OptionalKeys<T> = { [K in keyof T]-?: {} extends Pick<T, K> ? K : never}[keyof T]
interface User { id: number name: string age?: number email?: string}
type Required = RequiredKeys<User> // 'id' | 'name'type Optional = OptionalKeys<User> // 'age' | 'email'联合转交叉#
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ( k: infer I) => void ? I : never
type U = { a: string } | { b: number }type I = UnionToIntersection<U>// { a: string } & { b: number }联合转元组#
type UnionToTuple<T, Last = LastOfUnion<T>> = [T] extends [never] ? [] : [...UnionToTuple<Exclude<T, Last>>, Last]
type LastOfUnion<T> = UnionToIntersection<T extends any ? () => T : never> extends () => infer R ? R : never
type Tuple = UnionToTuple<'a' | 'b' | 'c'>// ['a', 'b', 'c']Promise 值提取#
type PromiseValue<T> = T extends Promise<infer V> ? V extends Promise<any> ? PromiseValue<V> : V : T
type P = PromiseValue<Promise<Promise<string>>>// string类型挑战示例#
实现 Pick#
type MyPick<T, K extends keyof T> = { [P in K]: T[P]}
interface Todo { title: string description: string completed: boolean}
type TodoPreview = MyPick<Todo, 'title' | 'completed'>// { title: string; completed: boolean }实现 Readonly#
type MyReadonly<T> = { readonly [K in keyof T]: T[K]}实现 Exclude#
type MyExclude<T, U> = T extends U ? never : T
type Result = MyExclude<'a' | 'b' | 'c', 'a'>// 'b' | 'c'实现 ReturnType#
type MyReturnType<T extends (...args: any) => any> = T extends ( ...args: any) => infer R ? R : never
function fn() { return { x: 10, y: 20 }}
type R = MyReturnType<typeof fn>// { x: number; y: number }调试技巧#
类型展开#
// 展开类型以便查看type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never
type ExpandRecursive<T> = T extends object ? T extends infer O ? { [K in keyof O]: ExpandRecursive<O[K]> } : never : T
// 使用type Complex = { a: string } & { b: number }type Expanded = Expand<Complex>// { a: string; b: number }类型断言#
// 检查类型相等type Expect<T extends true> = Ttype Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2 ? true : false
// 测试type Test1 = Expect<Equal<MyPick<Todo, 'title'>, { title: string }>>type Test2 = Expect<Equal<1, 1>>常见问题#
🙋 递归深度限制?#
// TypeScript 有递归深度限制(约 1000 层)// 使用尾递归优化type DeepFlat<T, Acc extends unknown[] = []> = T extends [ infer First, ...infer Rest,] ? First extends unknown[] ? DeepFlat<[...First, ...Rest], Acc> : DeepFlat<Rest, [...Acc, First]> : Acc🙋 如何处理联合类型分发?#
// 联合类型会自动分发type ToArray<T> = T extends any ? T[] : nevertype Result = ToArray<string | number> // string[] | number[]
// 禁止分发type ToArrayNoDistribute<T> = [T] extends [any] ? T[] : nevertype Result2 = ToArrayNoDistribute<string | number> // (string | number)[]总结#
| 技巧 | 用途 | 示例 |
|---|---|---|
| 条件类型 | 类型分支 | T extends U ? X : Y |
| 映射类型 | 遍历属性 | { [K in keyof T]: T[K] } |
| 递归类型 | 嵌套处理 | T extends [infer F, ...infer R] |
| 模板字面量 | 字符串操作 | `${T}` |
| infer | 模式匹配 | T extends Promise<infer U> |
下一篇我们将学习 TypeScript 最佳实践,了解如何在实际项目中更好地使用 TypeScript。