工具类型实现原理
内置工具类型的实现与自定义
概述
TypeScript 内置了丰富的工具类型(Utility Types),它们基于映射类型、条件类型和模板字面量类型等高级特性实现。理解这些工具类型的实现原理,不仅能帮助更好地使用它们,还能为自定义工具类型提供设计思路。本文将逐一解析内置工具类型的实现,并展示如何基于相同原理构建自定义工具类型,覆盖深度操作、键提取、值类型转换等常见需求。
基础概念
映射类型(Mapped Type):基于旧类型创建新类型的语法,通过遍历键集合并变换每个属性的类型来构建新类型。语法为 { [P in K]: T }。
条件类型(Conditional Type):根据类型关系选择不同结果的类型运算,语法为 T extends U ? X : Y。配合 infer 关键字可以在条件分支中提取类型。
键修饰符:映射类型支持 ?(可选)、-?(移除可选)、readonly(只读)、-readonly(移除只读)等修饰符,用于变换属性的可选性和可变性。
模板字面量类型:基于模板字符串构造类型,语法为 `prefix${Type}suffix`,常用于生成键名变换的映射类型。
快速上手
属性修饰工具类型
// Partial:将所有属性变为可选
type Partial<T> = { [P in keyof T]?: T[P] };
// Required:将所有属性变为必选
type Required<T> = { [P in keyof T]-?: T[P] };
// Readonly:将所有属性变为只读
type Readonly<T> = { readonly [P in keyof T]: T[P] };
// 使用示例
interface User {
name: string;
age: number;
email: string;
}
type PartialUser = Partial<User>;
// { name?: string; age?: number; email?: string }
type RequiredUser = Required<{ name?: string }>;
// { name: string }
属性选择工具类型
// Pick:从类型中选取部分属性
type Pick<T, K extends keyof T> = { [P in K]: T[P] };
// Omit:从类型中排除部分属性
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
// 使用示例
interface Todo {
title: string;
description: string;
completed: boolean;
}
type TodoPreview = Pick<Todo, 'title' | 'completed'>;
// { title: string; completed: boolean }
type TodoInfo = Omit<Todo, 'completed'>;
// { title: string; description: string }
详细用法
联合类型操作
// Exclude:从联合类型中排除成员
type Exclude<U, E> = U extends E ? never : U;
// Extract:从联合类型中提取成员
type Extract<U, E> = U extends E ? U : never;
// NonNullable:排除 null 和 undefined
type NonNullable<T> = T extends null | undefined ? never : T;
// 使用示例
type Status = 'active' | 'inactive' | 'pending' | null;
type ActiveStatus = Exclude<Status, null>;
// 'active' | 'inactive' | 'pending'
type NullableStatus = Extract<Status, null>;
// null
type DefiniteStatus = NonNullable<Status>;
// 'active' | 'inactive' | 'pending'
函数类型操作
// ReturnType:获取函数返回值类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
// Parameters:获取函数参数类型元组
type Parameters<T> = T extends (...args: infer P) => any ? P : never;
// ConstructorParameters:获取构造函数参数类型
type ConstructorParameters<T> = T extends new (...args: infer P) => any ? P : never;
// InstanceType:获取构造函数实例类型
type InstanceType<T> = T extends new (...args: any[]) => infer I ? I : never;
// 使用示例
function createUser(name: string, age: number): { id: number; name: string; age: number } {
return { id: Date.now(), name, age };
}
type UserReturn = ReturnType<typeof createUser>;
// { id: number; name: string; age: number }
type UserParams = Parameters<typeof createUser>;
// [string, number]
class DataService {
constructor(
private url: string,
private timeout: number
) {}
async fetch(): Promise<void> {}
}
type ServiceParams = ConstructorParameters<typeof DataService>;
// [string, number]
type ServiceInstance = InstanceType<typeof DataService>;
// DataService
Record 与 Awaited
// Record:构造键值对类型
type Record<K extends string | number | symbol, V> = { [P in K]: V };
// Awaited:递归展开 Promise
type Awaited<T> = T extends Promise<infer U> ? Awaited<U> : T;
// 使用示例
type UserRole = Record<'admin' | 'editor' | 'viewer', { permissions: string[] }>;
// {
// admin: { permissions: string[] };
// editor: { permissions: string[] };
// viewer: { permissions: string[] };
// }
type DeepPromise = Promise<Promise<Promise<string>>>;
type Unwrapped = Awaited<DeepPromise>; // string
常见场景
深度工具类型
// 深度 Partial
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? (T[P] extends Function ? T[P] : DeepPartial<T[P]>) : T[P];
};
// 深度 Required
type DeepRequired<T> = {
[P in keyof T]-?: T[P] extends object
? T[P] extends Function
? T[P]
: DeepRequired<T[P]>
: T[P];
};
// 深度 Readonly
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object
? T[P] extends Function
? T[P]
: DeepReadonly<T[P]>
: T[P];
};
// 使用示例
interface Config {
api: { baseURL: string; timeout: number };
features: { darkMode: boolean; analytics: boolean };
}
type PartialConfig = DeepPartial<Config>;
// { api?: { baseURL?: string; timeout?: number }; features?: { darkMode?: boolean; analytics?: boolean } }
type ReadonlyConfig = DeepReadonly<Config>;
// { readonly api: { readonly baseURL: string; readonly timeout: number }; ... }
键与值提取
// 提取值类型
type ValueOf<T> = T[keyof T];
// 提取可选键
type OptionalKeys<T> = {
[K in keyof T]-?: {} extends Pick<T, K> ? K : never;
}[keyof T];
// 提取必选键
type RequiredKeys<T> = {
[K in keyof T]-?: {} extends Pick<T, K> ? never : K;
}[keyof T];
// 使用示例
interface Article {
title: string;
content: string;
tags?: string[];
author?: string;
}
type ArticleValues = ValueOf<Article>;
// string | string[] | undefined
type ArticleOptionalKeys = OptionalKeys<Article>;
// 'tags' | 'author'
type ArticleRequiredKeys = RequiredKeys<Article>;
// 'title' | 'content'
不可变类型
// 递归不可变
type Immutable<T> = {
readonly [P in keyof T]: T[P] extends object
? T[P] extends Function
? T[P]
: Immutable<T[P]>
: T[P];
};
// 可变版本(移除所有 readonly)
type Mutable<T> = {
-readonly [P in keyof T]: T[P] extends object
? T[P] extends Function
? T[P]
: Mutable<T[P]>
: T[P];
};
// 使用示例
interface State {
readonly id: number;
readonly items: readonly string[];
}
type MutableState = Mutable<State>;
// { id: number; items: string[] }
注意事项
- Function 过滤:深度工具类型中必须过滤 Function,否则函数的属性(如 call、apply)也会被递归处理,导致类型异常。
- 循环引用:递归工具类型可能遇到循环引用。TypeScript 支持一定深度的递归,但过深的递归会导致编译错误。
- 性能影响:复杂的工具类型(特别是递归类型)会显著增加编译时间。在大型项目中应谨慎使用,避免过度嵌套。
- never 的传播:在条件类型中,如果条件判断结果为 never,它会在联合类型中被自动忽略。这是设计工具类型时需要考虑的行为。
进阶用法
品牌 类型
// 使用交叉类型创建品牌类型
type Brand<T, B> = T & { __brand: B };
type USD = Brand<number, 'USD'>;
type EUR = Brand<number, 'EUR'>;
// 不同品牌类型不能互相赋值
const usd: USD = 100 as USD;
const eur: EUR = 100 as EUR;
// usd = eur; // 错误:不能将 EUR 赋值给 USD
// 安全的货币运算
function addUSD(a: USD, b: USD): USD {
return (a + b) as USD;
}
addUSD(usd, usd); // 正确
// addUSD(usd, eur); // 错误:类型不匹配
类型构建器
// 构建器模式:逐步构建复杂类型
type Builder<T, Required extends keyof T = never> = {
[K in Exclude<keyof T, Required>]?: T[K];
} & {
[K in Required]: T[K];
} & {
with<K extends keyof T>(key: K, value: T[K]): Builder<T, Required | K>;
build: Required extends keyof T ? () => T : never;
};
// 简化版本
type StrictBuilder<T> = {
[K in keyof T]?: T[K] | ((prev: T[K] | undefined) => T[K]);
};
function build<T>(spec: StrictBuilder<T>): T {
const result = {} as T;
for (const [key, value] of Object.entries(spec)) {
(result as any)[key] = typeof value === 'function' ? value((result as any)[key]) : value;
}
return result;
}