TypeScript 开发指南 / 10 - 工具类型
工具类型
TypeScript 内置了一系列泛型工具类型(Utility Types),用于常见的类型变换。
Partial<T>
将类型 T 的所有属性变为可选:
interface User {
id: number;
name: string;
email: string;
age: number;
}
// 所有属性都变成可选
type PartialUser = Partial<User>;
// 等价于:
// {
// id?: number;
// name?: string;
// email?: string;
// age?: number;
// }
// 使用场景:更新函数(只更新传入的字段)
function updateUser(id: number, updates: Partial<User>): void {
// 只更新传入的字段
}
updateUser(1, { name: "Bob" }); // ✅ 只更新 name
updateUser(1, { name: "Bob", age: 30 }); // ✅ 更新多个字段
实现原理
// Partial 的实现
type MyPartial<T> = {
[P in keyof T]?: T[P];
};
Required<T>
将类型 T 的所有属性变为必选:
interface Config {
host?: string;
port?: number;
ssl?: boolean;
}
// 所有属性变成必选
type RequiredConfig = Required<Config>;
// {
// host: string;
// port: number;
// ssl: boolean;
// }
const config: RequiredConfig = {
host: "localhost",
port: 3000,
ssl: false
};
实现原理
type MyRequired<T> = {
[P in keyof T]-?: T[P]; // -? 移除可选修饰符
};
Readonly<T>
将类型 T 的所有属性变为只读:
interface State {
count: number;
items: string[];
}
type ReadonlyState = Readonly<State>;
const state: ReadonlyState = { count: 0, items: [] };
state.count = 1; // ❌ 错误:只读属性
state.items = []; // ❌ 错误
实现原理
type MyReadonly<T> = {
readonly [P in keyof T]: T[P];
};
Pick<T, K>
从类型 T 中选取指定的属性 K:
interface User {
id: number;
name: string;
email: string;
password: string;
createdAt: Date;
}
// 只选取 id 和 name
type UserBasic = Pick<User, "id" | "name">;
// {
// id: number;
// name: string;
// }
// 使用场景:API 响应只返回部分字段
function getUserBasic(id: number): UserBasic {
return { id, name: "Alice" };
}
实现原理
type MyPick<T, K extends keyof T> = {
[P in K]: T[P];
};
Omit<T, K>
从类型 T 中排除指定的属性 K:
interface User {
id: number;
name: string;
email: string;
password: string;
}
// 排除 password
type SafeUser = Omit<User, "password">;
// {
// id: number;
// name: string;
// email: string;
// }
// 使用场景:API 响应排除敏感字段
function getSafeUser(user: User): SafeUser {
const { password, ...rest } = user;
return rest;
}
实现原理
type MyOmit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
Record<K, T>
构造一个键类型为 K、值类型为 T 的对象类型:
// 基本用法
type UserMap = Record<string, User>;
const users: UserMap = {
"user-1": { id: 1, name: "Alice", email: "[email protected]", password: "..." },
"user-2": { id: 2, name: "Bob", email: "[email protected]", password: "..." }
};
// 字面量键
type PageInfo = Record<"home" | "about" | "contact", { title: string; url: string }>;
const pages: PageInfo = {
home: { title: "首页", url: "/" },
about: { title: "关于", url: "/about" },
contact: { title: "联系", url: "/contact" }
};
实现原理
type MyRecord<K extends keyof any, T> = {
[P in K]: T;
};
Exclude<T, U>
从联合类型 T 中排除可以赋值给 U 的类型:
type Status = "active" | "inactive" | "banned" | "deleted";
// 排除 "banned" 和 "deleted"
type ActiveStatus = Exclude<Status, "banned" | "deleted">;
// "active" | "inactive"
// 排除特定类型
type StringOrNumber = string | number | boolean | null;
type NotNull = Exclude<StringOrNumber, null>;
// string | number | boolean
实现原理
type MyExclude<T, U> = T extends U ? never : T;
Extract<T, U>
从联合类型 T 中提取可以赋值给 U 的类型:
type Status = "active" | "inactive" | "banned" | "deleted";
// 提取包含 "ed" 的类型
type DeletedStatus = Extract<Status, `${string}ed`>;
// "banned" | "deleted"
// 提取特定类型
type StringOrNumber = string | number | boolean;
type OnlyString = Extract<StringOrNumber, string>;
// string
实现原理
type MyExtract<T, U> = T extends U ? T : never;
NonNullable<T>
从类型 T 中排除 null 和 undefined:
type MaybeString = string | null | undefined;
type DefinitelyString = NonNullable<MaybeString>;
// string
// 使用场景
function process(value: NonNullable<string | null>): void {
// value: string
console.log(value.toUpperCase());
}
实现原理
type MyNonNullable<T> = T & {};
// 或者
type MyNonNullable2<T> = T extends null | undefined ? never : T;
ReturnType<T>
获取函数类型 T 的返回值类型:
function getUser() {
return { id: 1, name: "Alice", email: "[email protected]" };
}
type UserReturn = ReturnType<typeof getUser>;
// { id: number; name: string; email: string }
// 异步函数
async function fetchData() {
const response = await fetch("/api/data");
return response.json();
}
type Data = ReturnType<typeof fetchData>; // Promise<any>
实现原理
type MyReturnType<T extends (...args: any) => any> = T extends (
...args: any
) => infer R
? R
: any;
Parameters<T>
获取函数类型 T 的参数类型元组:
function createUser(name: string, age: number, email: string): User {
return { id: 1, name, age, email, password: "" };
}
type CreateUserParams = Parameters<typeof createUser>;
// [name: string, age: number, email: string]
// 使用场景:包装函数
function withLogging<T extends (...args: any[]) => any>(
fn: T
): (...args: Parameters<T>) => ReturnType<T> {
return (...args) => {
console.log("调用参数:", args);
return fn(...args);
};
}
const loggedCreateUser = withLogging(createUser);
ConstructorParameters<T>
获取构造函数的参数类型:
class User {
constructor(
public name: string,
public age: number
) {}
}
type UserConstructorParams = ConstructorParameters<typeof User>;
// [name: string, age: number]
// 使用场景:工厂函数
function createInstance<T extends new (...args: any[]) => any>(
ctor: T,
...args: ConstructorParameters<T>
): InstanceType<T> {
return new ctor(...args);
}
const user = createInstance(User, "Alice", 25);
InstanceType<T>
获取构造函数的实例类型:
class User {
name: string = "";
greet() { return "Hello"; }
}
type UserInstance = InstanceType<typeof User>;
// User
// 工厂模式
type Constructor<T = {}> = new (...args: any[]) => T;
function createFactory<T>(ctor: Constructor<T>): () => T {
return () => new ctor();
}
Awaited<T>
递归解包 Promise 类型(TypeScript 4.5+):
type A = Awaited<Promise<string>>; // string
type B = Awaited<Promise<Promise<number>>>; // number
type C = Awaited<boolean | Promise<number>>; // boolean | number
// 使用场景
async function getData(): Promise<string> {
return "hello";
}
type Data = Awaited<ReturnType<typeof getData>>; // string
组合使用工具类型
interface FullUser {
id: number;
name: string;
email: string;
password: string;
role: "admin" | "user";
createdAt: Date;
updatedAt: Date;
}
// 创建用户(排除 id 和时间戳)
type CreateUserData = Omit<FullUser, "id" | "createdAt" | "updatedAt">;
// 更新用户(所有字段可选,排除 id)
type UpdateUserData = Omit<Partial<FullUser>, "id">;
// 用户列表项(只包含基本信息)
type UserListItem = Pick<FullUser, "id" | "name" | "email" | "role">;
// API 响应(排除密码)
type SafeUser = Omit<FullUser, "password">;
type UserResponse = {
data: SafeUser;
status: number;
};
自定义工具类型
// DeepPartial - 递归可选
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
interface Config {
database: {
host: string;
port: number;
credentials: {
username: string;
password: string;
};
};
cache: {
ttl: number;
};
}
// 所有嵌套属性都可选
type PartialConfig = DeepPartial<Config>;
const config: PartialConfig = {
database: {
host: "localhost"
// 其他字段可选
}
};
// DeepReadonly - 递归只读
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};
// Mutable - 移除 readonly
type Mutable<T> = {
-readonly [P in keyof T]: T[P];
};
// RequiredKeys - 获取所有必选属性的键
type RequiredKeys<T> = {
[K in keyof T]-?: {} extends Pick<T, K> ? never : K;
}[keyof T];
// OptionalKeys - 获取所有可选属性的键
type OptionalKeys<T> = {
[K in keyof T]-?: {} extends Pick<T, K> ? K : never;
}[keyof T];
业务场景:表单类型
interface FormFields {
username: string;
email: string;
password: string;
confirmPassword: string;
age: number;
bio: string;
}
// 表单状态
type FormState = {
values: FormFields;
errors: Partial<Record<keyof FormFields, string[]>>;
touched: Partial<Record<keyof FormFields, boolean>>;
isSubmitting: boolean;
};
// 表单变更
type FormChangeEvent = {
field: keyof FormFields;
value: FormFields[keyof FormFields];
};
// 表单验证规则
type ValidationRules = {
[K in keyof FormFields]?: {
required?: boolean;
minLength?: number;
maxLength?: number;
pattern?: RegExp;
custom?: (value: FormFields[K]) => string | null;
};
};
注意事项
- 工具类型只在编译时存在——运行时没有工具类型的概念
- Partial 适用于更新操作——只传入需要修改的字段
- Omit 比 Pick 更常用——通常排除敏感字段比选取字段更常见
- 组合使用工具类型可以实现复杂的类型变换
- 自定义工具类型可以放在
types/目录下统一管理