TypeScript-03-类型体操与工具类型
类型体操是 TypeScript 高级用法的精髓,掌握它能让你写出更加灵活和类型安全的代码。
1. 内置工具类型详解
1.1 属性修饰工具类型
typescript
// Partial<T> - 将所有属性变为可选
type Partial<T> = {
[P in keyof T]?: T[P];
};
interface User {
id: number;
name: string;
email: string;
}
type PartialUser = Partial<User>;
// { id?: number; name?: string; email?: string }
// 实际应用:更新操作
function updateUser(id: number, updates: Partial<User>) {
// updates 可以只包含部分字段
}
updateUser(1, { name: "Alice" }); // ✓
// Required<T> - 将所有属性变为必选
type Required<T> = {
[P in keyof T]-?: T[P];
};
interface Config {
host?: string;
port?: number;
}
type RequiredConfig = Required<Config>;
// { host: string; port: number }
// Readonly<T> - 将所有属性变为只读
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
type ReadonlyUser = Readonly<User>;
// { readonly id: number; readonly name: string; readonly email: string }
// Mutable<T> - 移除 readonly(自定义)
type Mutable<T> = {
-readonly [P in keyof T]: T[P];
};1.2 属性选择工具类型
typescript
// Pick<T, K> - 从 T 中选择部分属性
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
interface User {
id: number;
name: string;
email: string;
password: string;
createdAt: Date;
}
type PublicUser = Pick<User, "id" | "name" | "email">;
// { id: number; name: string; email: string }
// Omit<T, K> - 从 T 中排除部分属性
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
type UserWithoutPassword = Omit<User, "password">;
// { id: number; name: string; email: string; createdAt: Date }
// Record<K, T> - 创建键类型为 K,值类型为 T 的对象类型
type Record<K extends keyof any, T> = {
[P in K]: T;
};
type UserRoles = Record<string, string[]>;
// { [key: string]: string[] }
type HttpStatus = Record<number, string>;
// { [key: number]: string }
// 实际应用
const errorMessages: Record<number, string> = {
400: "Bad Request",
401: "Unauthorized",
404: "Not Found",
500: "Internal Server Error",
};1.3 联合类型工具
typescript
// Exclude<T, U> - 从 T 中排除可分配给 U 的类型
type Exclude<T, U> = T extends U ? never : T;
type T0 = Exclude<"a" | "b" | "c", "a">;
// "b" | "c"
type T1 = Exclude<string | number | boolean, boolean>;
// string | number
// Extract<T, U> - 从 T 中提取可分配给 U 的类型
type Extract<T, U> = T extends U ? T : never;
type T2 = Extract<"a" | "b" | "c", "a" | "f">;
// "a"
type T3 = Extract<string | number | (() => void), Function>;
// () => void
// NonNullable<T> - 从 T 中排除 null 和 undefined
type NonNullable<T> = T extends null | undefined ? never : T;
type T4 = NonNullable<string | null | undefined>;
// string1.4 函数工具类型
typescript
// ReturnType<T> - 获取函数返回类型
type ReturnType<T extends (...args: any) => any> = T extends (
...args: any
) => infer R
? R
: any;
function getUser() {
return { id: 1, name: "Alice" };
}
type UserType = ReturnType<typeof getUser>;
// { id: number; name: string }
// Parameters<T> - 获取函数参数类型(元组)
type Parameters<T extends (...args: any) => any> = T extends (
...args: infer P
) => any
? P
: never;
function createUser(name: string, age: number, email: string) {
return { name, age, email };
}
type CreateUserParams = Parameters<typeof createUser>;
// [string, number, string]
// ConstructorParameters<T> - 获取构造函数参数类型
type ConstructorParameters<T extends abstract new (...args: any) => any> =
T extends abstract new (...args: infer P) => any ? P : never;
class Person {
constructor(public name: string, public age: number) {}
}
type PersonConstructorParams = ConstructorParameters<typeof Person>;
// [string, number]
// InstanceType<T> - 获取构造函数实例类型
type InstanceType<T extends abstract new (...args: any) => any> =
T extends abstract new (...args: any) => infer R ? R : any;
type PersonInstance = InstanceType<typeof Person>;
// Person
// ThisParameterType<T> - 提取函数的 this 参数类型
type ThisParameterType<T> = T extends (this: infer U, ...args: any[]) => any
? U
: unknown;
// OmitThisParameter<T> - 移除函数的 this 参数
type OmitThisParameter<T> = unknown extends ThisParameterType<T>
? T
: T extends (...args: infer A) => infer R
? (...args: A) => R
: T;1.5 Promise 工具类型
typescript
// Awaited<T> - 递归解包 Promise(TypeScript 4.5+)
type Awaited<T> = T extends null | undefined
? T
: T extends object & { then(onfulfilled: infer F): any }
? F extends (value: infer V, ...args: any) => any
? Awaited<V>
: never
: T;
type T0 = Awaited<Promise<string>>;
// string
type T1 = Awaited<Promise<Promise<number>>>;
// number
type T2 = Awaited<boolean | Promise<string>>;
// boolean | string2. 自定义工具类型
2.1 深度工具类型
typescript
// DeepPartial - 深度可选
type DeepPartial<T> = T extends object
? { [P in keyof T]?: DeepPartial<T[P]> }
: T;
interface NestedConfig {
database: {
host: string;
port: number;
credentials: {
username: string;
password: string;
};
};
cache: {
enabled: boolean;
ttl: number;
};
}
type PartialConfig = DeepPartial<NestedConfig>;
// 所有嵌套属性都变为可选
// DeepReadonly - 深度只读
type DeepReadonly<T> = T extends (infer U)[]
? DeepReadonlyArray<U>
: T extends object
? { readonly [P in keyof T]: DeepReadonly<T[P]> }
: T;
interface DeepReadonlyArray<T> extends ReadonlyArray<DeepReadonly<T>> {}
// DeepRequired - 深度必选
type DeepRequired<T> = T extends object
? { [P in keyof T]-?: DeepRequired<T[P]> }
: T;
// DeepMutable - 深度可变
type DeepMutable<T> = T extends object
? { -readonly [P in keyof T]: DeepMutable<T[P]> }
: T;2.2 对象操作工具类型
typescript
// KeysOfType - 获取指定类型的键
type KeysOfType<T, U> = {
[K in keyof T]: T[K] extends U ? K : never;
}[keyof T];
interface User {
id: number;
name: string;
email: string;
age: number;
isActive: boolean;
}
type StringKeys = KeysOfType<User, string>;
// "name" | "email"
type NumberKeys = KeysOfType<User, number>;
// "id" | "age"
// PickByType - 选择指定类型的属性
type PickByType<T, U> = {
[K in keyof T as T[K] extends U ? K : never]: T[K];
};
type UserStrings = PickByType<User, string>;
// { name: string; email: string }
// OmitByType - 排除指定类型的属性
type OmitByType<T, U> = {
[K in keyof T as T[K] extends U ? never : K]: T[K];
};
type UserWithoutStrings = OmitByType<User, string>;
// { id: number; age: number; isActive: boolean }
// Merge - 合并两个对象类型
type Merge<T, U> = {
[K in keyof T | keyof U]: K extends keyof U
? U[K]
: K extends keyof T
? T[K]
: never;
};
type A = { a: string; b: number };
type B = { b: string; c: boolean };
type Merged = Merge<A, B>;
// { a: string; b: string; c: boolean }
// Diff - 获取 T 有但 U 没有的属性
type Diff<T, U> = Pick<T, Exclude<keyof T, keyof U>>;
type DiffResult = Diff<A, B>;
// { a: string }
// Intersection - 获取 T 和 U 共有的属性
type Intersection<T, U> = Pick<T, Extract<keyof T, keyof U>>;
type CommonProps = Intersection<A, B>;
// { b: number }2.3 函数工具类型
typescript
// PromisifyFunction - 将函数返回值包装为 Promise
type PromisifyFunction<T extends (...args: any[]) => any> = (
...args: Parameters<T>
) => Promise<ReturnType<T>>;
function syncAdd(a: number, b: number): number {
return a + b;
}
type AsyncAdd = PromisifyFunction<typeof syncAdd>;
// (a: number, b: number) => Promise<number>
// Curry - 柯里化函数类型
type Curry<F extends (...args: any[]) => any> = Parameters<F> extends [
infer First,
...infer Rest
]
? Rest extends []
? F
: (arg: First) => Curry<(...args: Rest) => ReturnType<F>>
: F;
type CurriedAdd = Curry<(a: number, b: number, c: number) => number>;
// (arg: number) => (arg: number) => (arg: number) => number
// Promisify - 改变回调风格为 Promise 风格
type CallbackFunction<T> = (error: Error | null, result: T) => void;
type Promisify<T extends (...args: any[]) => void> = T extends (
...args: [...infer Args, CallbackFunction<infer R>]
) => void
? (...args: Args) => Promise<R>
: never;2.4 字符串工具类型
typescript
// Split - 字符串分割(TypeScript 4.1+)
type Split<
S extends string,
D extends string
> = S extends `${infer Head}${D}${infer Tail}`
? [Head, ...Split<Tail, D>]
: [S];
type Parts = Split<"a-b-c", "-">;
// ["a", "b", "c"]
// Join - 字符串连接
type Join<T extends string[], D extends string> = T extends []
? ""
: T extends [infer F extends string]
? F
: T extends [infer F extends string, ...infer R extends string[]]
? `${F}${D}${Join<R, D>}`
: string;
type Joined = Join<["a", "b", "c"], "-">;
// "a-b-c"
// TrimLeft - 去除左侧空白
type TrimLeft<S extends string> = S extends ` ${infer R}` ? TrimLeft<R> : S;
type TL = TrimLeft<" hello">;
// "hello"
// TrimRight - 去除右侧空白
type TrimRight<S extends string> = S extends `${infer R} ` ? TrimRight<R> : S;
// Trim - 去除两侧空白
type Trim<S extends string> = TrimLeft<TrimRight<S>>;
type Trimmed = Trim<" hello ">;
// "hello"
// Replace - 字符串替换
type Replace<
S extends string,
From extends string,
To extends string
> = From extends ""
? S
: S extends `${infer Head}${From}${infer Tail}`
? `${Head}${To}${Tail}`
: S;
type Replaced = Replace<"hello world", "world", "TypeScript">;
// "hello TypeScript"
// ReplaceAll - 全部替换
type ReplaceAll<
S extends string,
From extends string,
To extends string
> = From extends ""
? S
: S extends `${infer Head}${From}${infer Tail}`
? ReplaceAll<`${Head}${To}${Tail}`, From, To>
: S;
type ReplacedAll = ReplaceAll<"a-b-c-d", "-", "_">;
// "a_b_c_d"
// CamelCase - 转换为驼峰命名
type CamelCase<S extends string> = S extends `${infer Head}_${infer Tail}`
? `${Lowercase<Head>}${Capitalize<CamelCase<Tail>>}`
: Lowercase<S>;
type Camel = CamelCase<"hello_world_foo">;
// "helloWorldFoo"
// KebabCase - 转换为短横线命名
type KebabCase<S extends string> = S extends `${infer C}${infer T}`
? T extends Uncapitalize<T>
? `${Lowercase<C>}${KebabCase<T>}`
: `${Lowercase<C>}-${KebabCase<T>}`
: S;
type Kebab = KebabCase<"helloWorldFoo">;
// "hello-world-foo"3. 类型递归与循环
3.1 递归类型基础
typescript
// 数组长度类型
type Length<T extends readonly any[]> = T["length"];
type L = Length<[1, 2, 3]>;
// 3
// 数组第一个元素
type First<T extends readonly any[]> = T extends readonly [infer F, ...any[]]
? F
: never;
type F = First<[1, 2, 3]>;
// 1
// 数组最后一个元素
type Last<T extends readonly any[]> = T extends readonly [...any[], infer L]
? L
: never;
type L2 = Last<[1, 2, 3]>;
// 3
// 数组除第一个外的元素
type Tail<T extends readonly any[]> = T extends readonly [any, ...infer R]
? R
: [];
type T = Tail<[1, 2, 3]>;
// [2, 3]
// 数组除最后一个外的元素
type Init<T extends readonly any[]> = T extends readonly [...infer I, any]
? I
: [];
type I = Init<[1, 2, 3]>;
// [1, 2]3.2 递归数组操作
typescript
// Reverse - 数组反转
type Reverse<T extends any[]> = T extends [infer F, ...infer R]
? [...Reverse<R>, F]
: [];
type Reversed = Reverse<[1, 2, 3, 4]>;
// [4, 3, 2, 1]
// Flatten - 数组扁平化
type Flatten<T extends any[]> = T extends [infer F, ...infer R]
? F extends any[]
? [...Flatten<F>, ...Flatten<R>]
: [F, ...Flatten<R>]
: [];
type Flattened = Flatten<[1, [2, [3, 4]], 5]>;
// [1, 2, 3, 4, 5]
// Unique - 数组去重
type Includes<T extends any[], U> = T extends [infer F, ...infer R]
? Equal<F, U> extends true
? true
: Includes<R, U>
: false;
type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y
? 1
: 2
? true
: false;
type Unique<T extends any[], R extends any[] = []> = T extends [
infer F,
...infer Rest
]
? Includes<R, F> extends true
? Unique<Rest, R>
: Unique<Rest, [...R, F]>
: R;
type Uniqued = Unique<[1, 2, 2, 3, 3, 3]>;
// [1, 2, 3]
// Concat - 数组连接
type Concat<T extends any[], U extends any[]> = [...T, ...U];
type Concatenated = Concat<[1, 2], [3, 4]>;
// [1, 2, 3, 4]
// Push - 数组添加元素
type Push<T extends any[], U> = [...T, U];
type Pushed = Push<[1, 2], 3>;
// [1, 2, 3]
// Unshift - 数组头部添加
type Unshift<T extends any[], U> = [U, ...T];
type Unshifted = Unshift<[1, 2], 0>;
// [0, 1, 2]3.3 数字运算类型
typescript
// 构建指定长度的元组
type BuildTuple<
N extends number,
R extends any[] = []
> = R["length"] extends N ? R : BuildTuple<N, [...R, unknown]>;
type Tuple5 = BuildTuple<5>;
// [unknown, unknown, unknown, unknown, unknown]
// Add - 加法
type Add<A extends number, B extends number> = [
...BuildTuple<A>,
...BuildTuple<B>
]["length"];
type Sum = Add<3, 4>;
// 7 (实际类型为 number,但值是 7)
// Subtract - 减法
type Subtract<A extends number, B extends number> = BuildTuple<A> extends [
...BuildTuple<B>,
...infer R
]
? R["length"]
: never;
type Diff = Subtract<10, 3>;
// 7
// LessThan - 小于比较
type LessThan<
A extends number,
B extends number,
Arr extends any[] = []
> = Arr["length"] extends B
? false
: Arr["length"] extends A
? true
: LessThan<A, B, [...Arr, unknown]>;
type IsLess = LessThan<3, 5>;
// true
// Range - 生成数字范围
type Range<
Start extends number,
End extends number,
R extends number[] = []
> = Start extends End ? R : Range<Add<Start, 1> & number, End, [...R, Start]>;
type NumberRange = Range<0, 5>;
// [0, 1, 2, 3, 4]4. 实战类型体操
4.1 实现 Chainable 链式调用
typescript
type Chainable<T = {}> = {
option<K extends string, V>(
key: K extends keyof T ? never : K,
value: V
): Chainable<T & { [P in K]: V }>;
get(): T;
};
declare const config: Chainable;
const result = config
.option("foo", 123)
.option("bar", { value: "Hello" })
.option("name", "TypeScript")
.get();
// result 的类型:
// {
// foo: number;
// bar: { value: string };
// name: string;
// }4.2 实现 Vue 的 computed 类型推断
typescript
type GetComputed<C> = C extends Record<string, (...args: any[]) => any>
? { [K in keyof C]: ReturnType<C[K]> }
: never;
interface Options<D, C, M> {
data?: () => D;
computed?: C & ThisType<D & GetComputed<C> & M>;
methods?: M & ThisType<D & GetComputed<C> & M>;
}
declare function defineComponent<D, C, M>(
options: Options<D, C, M>
): ThisType<D & GetComputed<C> & M>;
// 使用示例
defineComponent({
data() {
return {
count: 0,
message: "Hello",
};
},
computed: {
doubleCount() {
return this.count * 2; // this.count 类型为 number
},
},
methods: {
increment() {
this.count++;
console.log(this.doubleCount); // 类型为 number
},
},
});4.3 实现 ParseQueryString
typescript
// 解析 URL 查询字符串为对象类型
type ParseQueryString<S extends string> = S extends ""
? {}
: S extends `${infer Param}&${infer Rest}`
? MergeParams<ParseParam<Param>, ParseQueryString<Rest>>
: ParseParam<S>;
type ParseParam<S extends string> = S extends `${infer Key}=${infer Value}`
? { [K in Key]: Value }
: {};
type MergeParams<T, U> = {
[K in keyof T | keyof U]: K extends keyof T
? K extends keyof U
? T[K] | U[K]
: T[K]
: K extends keyof U
? U[K]
: never;
};
type Query = ParseQueryString<"name=Alice&age=25&city=Beijing">;
// { name: "Alice"; age: "25"; city: "Beijing" }4.4 实现 JSON 类型
typescript
// JSON 值类型
type JSONValue =
| string
| number
| boolean
| null
| JSONValue[]
| { [key: string]: JSONValue };
// JSON 对象类型
type JSONObject = { [key: string]: JSONValue };
// JSON 数组类型
type JSONArray = JSONValue[];
// 类型安全的 JSON 解析
function parseJSON<T extends JSONValue>(json: string): T {
return JSON.parse(json) as T;
}
// 类型安全的 JSON 序列化
function stringifyJSON<T extends JSONValue>(value: T): string {
return JSON.stringify(value);
}4.5 实现 PathOf 获取对象路径
typescript
// 获取对象的所有路径
type PathOf<T, Prefix extends string = ""> = T extends object
? {
[K in keyof T & string]: T[K] extends object
? PathOf<T[K], `${Prefix}${K}.`> | `${Prefix}${K}`
: `${Prefix}${K}`;
}[keyof T & string]
: never;
interface DeepObject {
user: {
name: string;
profile: {
avatar: string;
bio: string;
};
};
settings: {
theme: string;
};
}
type Paths = PathOf<DeepObject>;
// "user" | "user.name" | "user.profile" | "user.profile.avatar" | "user.profile.bio" | "settings" | "settings.theme"
// 根据路径获取类型
type GetByPath<T, P extends string> = P extends `${infer K}.${infer Rest}`
? K extends keyof T
? GetByPath<T[K], Rest>
: never
: P extends keyof T
? T[P]
: never;
type AvatarType = GetByPath<DeepObject, "user.profile.avatar">;
// string
type ProfileType = GetByPath<DeepObject, "user.profile">;
// { avatar: string; bio: string }5. 类型体操挑战
挑战 1:实现 TupleToUnion
typescript
// 将元组转换为联合类型
type TupleToUnion<T extends any[]> = T[number];
type Union = TupleToUnion<[1, "a", true]>;
// 1 | "a" | true挑战 2:实现 UnionToTuple
typescript
// 将联合类型转换为元组(较复杂)
type UnionToIntersection<U> = (
U extends any ? (k: U) => void : never
) extends (k: infer I) => void
? I
: never;
type LastOfUnion<T> = UnionToIntersection<
T extends any ? () => T : never
> extends () => infer R
? R
: never;
type UnionToTuple<T, L = LastOfUnion<T>> = [T] extends [never]
? []
: [...UnionToTuple<Exclude<T, L>>, L];
type Tuple = UnionToTuple<"a" | "b" | "c">;
// ["a", "b", "c"] (顺序可能不同)挑战 3:实现 StringToUnion
typescript
// 将字符串转换为字符联合类型
type StringToUnion<S extends string> = S extends `${infer C}${infer Rest}`
? C | StringToUnion<Rest>
: never;
type CharUnion = StringToUnion<"hello">;
// "h" | "e" | "l" | "o"挑战 4:实现 PermString
typescript
// 字符串排列组合
type PermString<S extends string, U extends string = StringToUnion<S>> = [
U
] extends [never]
? ""
: U extends U
? `${U}${PermString<never, Exclude<StringToUnion<S>, U>>}`
: never;
// 简化版本
type Permutation<T, U = T> = [T] extends [never]
? []
: T extends T
? [T, ...Permutation<Exclude<U, T>>]
: never;
type Perms = Permutation<"A" | "B" | "C">;
// ["A", "B", "C"] | ["A", "C", "B"] | ["B", "A", "C"] | ...挑战 5:实现 IsAny
typescript
// 判断类型是否为 any
type IsAny<T> = 0 extends 1 & T ? true : false;
type T1 = IsAny<any>; // true
type T2 = IsAny<unknown>; // false
type T3 = IsAny<never>; // false
type T4 = IsAny<string>; // false挑战 6:实现 IsNever
typescript
// 判断类型是否为 never
type IsNever<T> = [T] extends [never] ? true : false;
type T1 = IsNever<never>; // true
type T2 = IsNever<undefined>; // false
type T3 = IsNever<null>; // false挑战 7:实现 IsUnion
typescript
// 判断类型是否为联合类型
type IsUnion<T, U = T> = T extends U
? [U] extends [T]
? false
: true
: never;
type T1 = IsUnion<string | number>; // true
type T2 = IsUnion<string>; // false
type T3 = IsUnion<never>; // false6. 小结
本章深入学习了 TypeScript 的类型体操技巧:
- 内置工具类型:Partial、Required、Pick、Omit、Record、Exclude、Extract 等
- 自定义工具类型:深度类型、对象操作、函数操作、字符串操作
- 类型递归:数组操作、数字运算
- 实战案例:链式调用、Vue computed、路径类型等
- 类型挑战:TupleToUnion、UnionToTuple、IsAny、IsNever 等
类型体操的核心是理解:
- 条件类型的分布式特性
- infer 推断的使用时机
- 递归类型的终止条件
- 模板字面量类型的字符串操作
下一章我们将学习 TypeScript 的工程化配置与最佳实践。