名称: typescript-utility-types 用户可调用: false 描述: 当使用TypeScript工具类型、映射类型和高级类型操作时使用。在创建灵活、类型安全的TypeScript代码时使用。 允许工具:
- Bash
- 读取
- 写入
- 编辑
TypeScript 工具类型
掌握 TypeScript 强大的类型系统,包括内置工具类型、映射类型、条件类型和高级类型操作技术,用于创建灵活、类型安全的代码。
内置工具类型
Partial 和 Required
interface User {
id: string;
name: string;
email: string;
age: number;
}
// Partial 使所有属性变为可选
type PartialUser = Partial<User>;
// { id?: string; name?: string; email?: string; age?: number; }
function updateUser(id: string, updates: Partial<User>): User {
const existingUser = getUser(id);
return { ...existingUser, ...updates };
}
updateUser('123', { name: 'John' }); // 有效
updateUser('123', { age: 30 }); // 有效
// Required 使所有属性变为必需
interface OptionalConfig {
host?: string;
port?: number;
timeout?: number;
}
type RequiredConfig = Required<OptionalConfig>;
// { host: string; port: number; timeout: number; }
function validateConfig(config: Required<OptionalConfig>): boolean {
return config.host.length > 0 && config.port > 0;
}
Pick 和 Omit
interface Article {
id: string;
title: string;
content: string;
author: string;
createdAt: Date;
updatedAt: Date;
views: number;
}
// Pick 选择特定属性
type ArticlePreview = Pick<Article, 'id' | 'title' | 'author'>;
// { id: string; title: string; author: string; }
function displayPreview(article: ArticlePreview): void {
console.log(`${article.title} by ${article.author}`);
}
// Omit 移除特定属性
type ArticleWithoutDates = Omit<Article, 'createdAt' | 'updatedAt'>;
// { id: string; title: string; content: string; author: string; views: number; }
// 结合 Pick 和 Omit
type ArticleMetadata = Pick<Article, 'id' | 'author' | 'createdAt'>;
type ArticleData = Omit<Article, 'id' | 'createdAt' | 'updatedAt'>;
Readonly 和 Record
// Readonly 使所有属性变为只读
type ReadonlyUser = Readonly<User>;
const user: ReadonlyUser = {
id: '1',
name: 'John',
email: 'john@example.com',
age: 30,
};
// user.name = 'Jane'; // 错误:无法分配给 'name',因为它是只读属性
// 嵌套对象的深度只读
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object
? DeepReadonly<T[P]>
: T[P];
};
// Record 创建具有特定键和值类型的对象类型
type UserRole = 'admin' | 'editor' | 'viewer';
type RolePermissions = Record<UserRole, string[]>;
// { admin: string[]; editor: string[]; viewer: string[]; }
const permissions: RolePermissions = {
admin: ['read', 'write', 'delete'],
editor: ['read', 'write'],
viewer: ['read'],
};
// 具有复杂类型的 Record
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
type RouteHandler = (req: Request) => Response;
type RouteHandlers = Record<HttpMethod, RouteHandler>;
Extract 和 Exclude
type Status = 'pending' | 'approved' | 'rejected' | 'cancelled';
// Extract 提取可分配给条件的类型
type CompletedStatus = Extract<Status, 'approved' | 'rejected'>;
// 'approved' | 'rejected'
// Exclude 排除可分配给条件的类型
type ActiveStatus = Exclude<Status, 'approved' | 'rejected' | 'cancelled'>;
// 'pending'
// 实际示例
type Shape =
| { kind: 'circle'; radius: number }
| { kind: 'square'; side: number }
| { kind: 'rectangle'; width: number; height: number };
type CircularShape = Extract<Shape, { kind: 'circle' }>;
// { kind: 'circle'; radius: number }
type NonCircularShape = Exclude<Shape, { kind: 'circle' }>;
// { kind: 'square'; side: number } | { kind: 'rectangle'; width: number; height: number }
ReturnType 和 Parameters
function createUser(name: string, age: number): User {
return {
id: generateId(),
name,
age,
email: `${name.toLowerCase()}@example.com`,
};
}
// ReturnType 提取函数的返回类型
type UserFromFunction = ReturnType<typeof createUser>;
// User
// Parameters 将参数类型提取为元组
type CreateUserParams = Parameters<typeof createUser>;
// [name: string, age: number]
// 与泛型函数一起使用
function processData<T>(data: T[]): { count: number; items: T[] } {
return { count: data.length, items: data };
}
type ProcessResult = ReturnType<typeof processData<User>>;
// { count: number; items: User[] }
映射类型
基本映射类型
// 创建所有属性为布尔值的类型
type Flags<T> = {
[P in keyof T]: boolean;
};
type UserFlags = Flags<User>;
// { id: boolean; name: boolean; email: boolean; age: boolean; }
// 创建所有属性可为空的类型
type Nullable<T> = {
[P in keyof T]: T[P] | null;
};
type NullableUser = Nullable<User>;
// { id: string | null; name: string | null; email: string | null; age: number | null; }
// 创建所有属性为函数的类型
type Getters<T> = {
[P in keyof T as `get${Capitalize<string & P>}`]: () => T[P];
};
type UserGetters = Getters<User>;
// { getId: () => string; getName: () => string; getEmail: () => string; getAge: () => number; }
映射类型修饰符
// 移除只读修饰符
type Mutable<T> = {
-readonly [P in keyof T]: T[P];
};
interface ReadonlyPerson {
readonly name: string;
readonly age: number;
}
type MutablePerson = Mutable<ReadonlyPerson>;
// { name: string; age: number; }
// 移除可选修饰符
type Concrete<T> = {
[P in keyof T]-?: T[P];
};
interface OptionalUser {
name?: string;
age?: number;
}
type ConcreteUser = Concrete<OptionalUser>;
// { name: string; age: number; }
// 添加可选修饰符
type Optional<T> = {
[P in keyof T]+?: T[P];
};
高级映射类型
// 转换属性类型
type Promisify<T> = {
[P in keyof T]: Promise<T[P]>;
};
type AsyncUser = Promisify<User>;
// { id: Promise<string>; name: Promise<string>; email: Promise<string>; age: Promise<number>; }
// 将值包装在对象中
type Boxed<T> = {
[P in keyof T]: { value: T[P] };
};
type BoxedUser = Boxed<User>;
// { id: { value: string }; name: { value: string }; ... }
// 创建代理类型
type Proxy<T> = {
get(): T;
set(value: T): void;
};
type ProxiedProperties<T> = {
[P in keyof T]: Proxy<T[P]>;
};
条件类型
基本条件类型
// T extends U ? X : Y
type IsString<T> = T extends string ? true : false;
type Test1 = IsString<string>; // true
type Test2 = IsString<number>; // false
// 嵌套条件类型
type TypeName<T> =
T extends string ? 'string' :
T extends number ? 'number' :
T extends boolean ? 'boolean' :
T extends undefined ? 'undefined' :
T extends Function ? 'function' :
'object';
type T0 = TypeName<string>; // 'string'
type T1 = TypeName<number>; // 'number'
type T2 = TypeName<() => void>; // 'function'
分配条件类型
// 条件类型在联合类型上分配
type ToArray<T> = T extends any ? T[] : never;
type StrOrNumArray = ToArray<string | number>;
// string[] | number[] (不是 (string | number)[])
// 非分配版本
type ToArrayNonDist<T> = [T] extends [any] ? T[] : never;
type StrOrNumArrayNonDist = ToArrayNonDist<string | number>;
// (string | number)[]
// 过滤掉 null 和 undefined
type NonNullable<T> = T extends null | undefined ? never : T;
type MaybeString = string | null | undefined;
type DefinitelyString = NonNullable<MaybeString>; // string
使用 infer 推断类型
// 推断返回类型
type GetReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
function example(): { x: number } {
return { x: 42 };
}
type ExampleReturn = GetReturnType<typeof example>;
// { x: number }
// 推断数组元素类型
type Flatten<T> = T extends Array<infer U> ? U : T;
type Str = Flatten<string[]>; // string
type Num = Flatten<number>; // number
// 推断 Promise 类型
type Awaited<T> = T extends Promise<infer U> ? U : T;
type PromiseString = Awaited<Promise<string>>; // string
type RegularString = Awaited<string>; // string
// 多个 infer 用法
type GetFirstArg<T> = T extends (first: infer F, ...args: any[]) => any
? F
: never;
function multi(a: string, b: number, c: boolean): void {}
type FirstArgType = GetFirstArg<typeof multi>; // string
模板文字类型
基本模板文字
type World = 'world';
type Greeting = `hello ${World}`; // 'hello world'
// 与联合类型一起使用
type Color = 'red' | 'blue' | 'green';
type Quantity = 'one' | 'two';
type ColoredQuantity = `${Quantity} ${Color}`;
// 'one red' | 'one blue' | 'one green' | 'two red' | 'two blue' | 'two green'
// 事件名称
type EventName = 'click' | 'focus' | 'blur';
type EventHandler = `on${Capitalize<EventName>}`;
// 'onClick' | 'onFocus' | 'onBlur'
字符串操作类型
// 内置字符串操作类型
type UppercaseGreeting = Uppercase<'hello'>; // 'HELLO'
type LowercaseGreeting = Lowercase<'HELLO'>; // 'hello'
type CapitalizedGreeting = Capitalize<'hello'>; // 'Hello'
type UncapitalizedGreeting = Uncapitalize<'Hello'>; // 'hello'
// 与模板文字结合使用
type GetterName<T extends string> = `get${Capitalize<T>}`;
type SetterName<T extends string> = `set${Capitalize<T>}`;
type UserNameGetter = GetterName<'name'>; // 'getName'
type UserNameSetter = SetterName<'name'>; // 'setName'
// 生成访问器方法
type Accessors<T> = {
[K in keyof T as GetterName<string & K>]: () => T[K];
} & {
[K in keyof T as SetterName<string & K>]: (value: T[K]) => void;
};
type UserAccessors = Accessors<User>;
// { getName: () => string; setName: (value: string) => void; ... }
模板文字模式匹配
// 从字符串模式中提取部分
type ExtractRouteParams<T extends string> =
T extends `${infer Start}/:${infer Param}/${infer Rest}`
? { [K in Param | keyof ExtractRouteParams<`/${Rest}`>]: string }
: T extends `${infer Start}/:${infer Param}`
? { [K in Param]: string }
: {};
type Route1 = ExtractRouteParams<'/users/:userId/posts/:postId'>;
// { userId: string; postId: string; }
type Route2 = ExtractRouteParams<'/posts/:id'>;
// { id: string; }
// 解析 CSS 属性
type CSSProperty =
| 'color'
| 'background-color'
| 'font-size'
| 'margin-top';
type CamelCase<S extends string> = S extends `${infer P1}-${infer P2}${infer P3}`
? `${P1}${Uppercase<P2>}${CamelCase<P3>}`
: S;
type CSSPropertyCamel = CamelCase<CSSProperty>;
// 'color' | 'backgroundColor' | 'fontSize' | 'marginTop'
键重映射
在映射类型中重映射键
// 过滤掉特定键
type OmitByType<T, U> = {
[P in keyof T as T[P] extends U ? never : P]: T[P];
};
interface Mixed {
name: string;
age: number;
isActive: boolean;
count: number;
}
type OnlyStrings = OmitByType<Mixed, number | boolean>;
// { name: string; }
// 用前缀重命名键
type Prefix<T, P extends string> = {
[K in keyof T as `${P}${string & K}`]: T[K];
};
type PrefixedUser = Prefix<User, 'user_'>;
// { user_id: string; user_name: string; user_email: string; user_age: number; }
// 转换为 getter 方法
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
type UserGetters = Getters<User>;
条件键重映射
// 仅包括匹配条件的键
type PickByType<T, U> = {
[P in keyof T as T[P] extends U ? P : never]: T[P];
};
type NumberProperties = PickByType<Mixed, number>;
// { age: number; count: number; }
// 根据类型重命名键
type RenameByType<T> = {
[K in keyof T as T[K] extends string
? `str_${string & K}`
: T[K] extends number
? `num_${string & K}`
: K]: T[K];
};
type RenamedMixed = RenameByType<Mixed>;
// { str_name: string; num_age: number; isActive: boolean; num_count: number; }
高级类型操作
递归类型
// JSON 类型
type JSONValue =
| string
| number
| boolean
| null
| JSONValue[]
| { [key: string]: JSONValue };
const json: JSONValue = {
name: 'John',
age: 30,
hobbies: ['reading', 'coding'],
address: {
city: 'New York',
coordinates: [40.7128, -74.006],
},
};
// 递归路径类型
type Path<T> = T extends object
? {
[K in keyof T]: K extends string
? T[K] extends object
? K | `${K}.${Path<T[K]>}`
: K
: never;
}[keyof T]
: never;
type UserPath = Path<User>;
// 'id' | 'name' | 'email' | 'age' | ...
// 深度 partial
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
联合和交集工具
// 联合转换为交集
type UnionToIntersection<U> = (
U extends any ? (x: U) => void : never
) extends (x: infer I) => void
? I
: never;
type Union = { a: string } | { b: number };
type Intersection = UnionToIntersection<Union>;
// { a: string } & { b: number }
// 获取必需键
type RequiredKeys<T> = {
[K in keyof T]-?: {} extends Pick<T, K> ? never : K;
}[keyof T];
interface PartialRequired {
required: string;
optional?: number;
}
type Required = RequiredKeys<PartialRequired>; // 'required'
// 获取可选键
type OptionalKeys<T> = {
[K in keyof T]-?: {} extends Pick<T, K> ? K : never;
}[keyof T];
type Optional = OptionalKeys<PartialRequired>; // 'optional'
函数类型工具
// 使函数异步
type Asyncify<T extends (...args: any[]) => any> = (
...args: Parameters<T>
) => Promise<ReturnType<T>>;
function syncFunction(x: number): string {
return x.toString();
}
type AsyncFunction = Asyncify<typeof syncFunction>;
// (x: number) => Promise<string>
// 柯里化函数类型
type Curry<T> = T extends (
arg: infer A,
...args: infer R
) => infer Return
? (arg: A) => R extends []
? Return
: Curry<(...args: R) => Return>
: never;
type CurriedFunction = Curry<(a: string, b: number, c: boolean) => void>;
// (arg: string) => (arg: number) => (arg: boolean) => void
构建器模式类型
// 类型安全的构建器模式
type Builder<T, R = {}> = {
[K in keyof T]: (
value: T[K]
) => Builder<Omit<T, K>, R & Pick<T, K>>;
} & (R extends T ? { build(): T } : {});
interface Config {
host: string;
port: number;
ssl: boolean;
}
function createBuilder<T>(): Builder<T> {
const values: Partial<T> = {};
const builder = new Proxy(
{},
{
get(_, prop) {
if (prop === 'build') {
return () => values as T;
}
return (value: any) => {
values[prop as keyof T] = value;
return builder;
};
},
}
) as Builder<T>;
return builder;
}
// 用法,具有完整的类型安全
const config = createBuilder<Config>()
.host('localhost')
.port(3000)
.ssl(true)
.build(); // 仅当所有属性设置后才可用
类型推断助手
Const 断言
// 没有 const 断言
const colors1 = ['red', 'blue', 'green'];
type Colors1 = typeof colors1; // string[]
// 有 const 断言
const colors2 = ['red', 'blue', 'green'] as const;
type Colors2 = typeof colors2; // readonly ['red', 'blue', 'green']
type Color = Colors2[number]; // 'red' | 'blue' | 'green'
// 带有 const 断言的对象
const config = {
api: {
url: 'https://api.example.com',
timeout: 5000,
},
} as const;
type ConfigUrl = typeof config.api.url; // 'https://api.example.com'
用户定义类型守卫的类型守卫
function isString(value: unknown): value is string {
return typeof value === 'string';
}
function isUser(value: unknown): value is User {
return (
typeof value === 'object' &&
value !== null &&
'id' in value &&
'name' in value &&
'email' in value
);
}
// 泛型类型守卫工厂
function hasProperty<K extends string>(
key: K
): <T>(obj: T) => obj is T & Record<K, unknown> {
return (obj): obj is T & Record<K, unknown> => {
return typeof obj === 'object' && obj !== null && key in obj;
};
}
const hasName = hasProperty('name');
if (hasName(someObject)) {
console.log(someObject.name); // 类型安全访问
}
最佳实践
-
首选内置工具类型:在创建自定义类型之前,使用 TypeScript 的内置工具类型(Partial、Pick、Omit 等),以提高可读性。
-
使用 Const 断言:当你需要字面量类型而不是扩展类型时,对数组和对象应用 const 断言。
-
保持类型简单:避免过于复杂的类型转换。如果一个类型变得难以理解,考虑重构或使用多个更简单的类型。
-
记录复杂类型:为不明显的类型转换添加注释,特别是对于映射类型和条件类型。
-
利用类型推断:尽可能让 TypeScript 推断类型,而不是到处显式声明。
-
使用模板文字类型处理字符串:对于字符串模式和连接,模板文字类型提供普通字符串无法提供的类型安全性。
-
优先使用 Type 而非 Interface 作为工具:对于工具类型和映射类型,使用类型别名,因为它们比接口更灵活。
-
测试你的类型:使用类型断言编写测试用例,以确保类型按预期行为。
-
避免类型体操:不要仅仅因为你能够创建复杂类型。专注于为代码增加价值和清晰度的类型。
-
使用区分联合类型:对于变体类型,使用带有字面类型字段的区分联合类型,以获得更好的类型收窄。
常见陷阱
-
类型过于复杂:创建过于复杂的类型会使代码难以理解,并可能减慢 TypeScript 编译器的速度。
-
忽略类型分配:忘记条件类型在联合类型上分配可能导致意外的类型结果。
-
误用 ReturnType 与泛型:在没有提供类型参数的情况下在泛型函数上使用 ReturnType 会丢失类型信息。
-
循环类型引用:创建循环类型依赖可能导致 TypeScript 错误或无限类型递归。
-
过度使用 any:在工具类型中使用 any 会破坏其目的,并失去类型安全的好处。
-
不理解映射类型修饰符:误用 + 和 - 修饰符或忘记它们可能导致意外的只读/可选行为。
-
模板文字性能问题:具有许多联合类型的复杂模板文字类型可能显著减慢类型检查。
-
忘记 as const:当需要字面量类型时,不使用 const 断言会导致扩展类型,失去特异性。
-
条件类型不匹配:编写从不匹配或总是匹配的条件类型条件会使类型无用。
-
工具类型过度杀伤:为可以直接表达的简单操作创建工具类型会使代码更难阅读。
何时使用此技能
在以下情况使用 TypeScript 工具类型:
- 无需重复即可转换现有类型
- 创建类型安全的 API 和库
- 构建通用、可重用的类型工具
- 在编译时强制执行类型约束
- 从运行时值生成类型
- 创建类型安全的构建器和流畅 API
- 使用类型建模复杂领域逻辑
- 以类型安全方式实现设计模式
- 减少类型维护负担
- 提供更好的 IDE 自动完成和错误消息
此技能对于库作者、框架开发者、TypeScript 专家以及任何构建类型安全、可维护的 TypeScript 应用程序的人至关重要。
资源
官方文档
- TypeScript 手册 - 工具类型: https://www.typescriptlang.org/docs/handbook/utility-types.html
- TypeScript 手册 - 映射类型: https://www.typescriptlang.org/docs/handbook/2/mapped-types.html
- TypeScript 手册 - 条件类型: https://www.typescriptlang.org/docs/handbook/2/conditional-types.html
- TypeScript 手册 - 模板文字类型: https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html
学习资源
- Type 挑战:https://github.com/type-challenges/type-challenges
- TypeScript 深入理解:https://basarat.gitbook.io/typescript/
- Total TypeScript:https://www.totaltypescript.com/
- 由 Dan Vanderkam 编写的 Effective TypeScript
工具和库
- ts-toolbelt:https://github.com/millsp/ts-toolbelt - 高级类型工具
- type-fest:https://github.com/sindresorhus/type-fest - 基本的 TypeScript 类型
- utility-types:https://github.com/piotrwitek/utility-types - 工具类型集合
- TypeScript Playground:https://www.typescriptlang.org/play - 交互式类型探索
社区
- TypeScript GitHub 讨论: https://github.com/microsoft/TypeScript/discussions
- Stack Overflow TypeScript 标签: https://stackoverflow.com/questions/tagged/typescript
- r/typescript:https://www.reddit.com/r/typescript/