
什么是实用类型?
TypeScript 提供了一组泛型实用类型,用于将现有类型转换为新类型。它们消除了重复的类型声明,让你无需重复即可从现有类型派生新类型。
所有实用类型都内置于 TypeScript 中——无需导入。
Partial
将 T 的所有属性变为可选。
interface User {
id: number;
name: string;
email: string;
role: 'admin' | 'user';
}
type PartialUser = Partial<User>;
// {
// id?: number;
// name?: string;
// email?: string;
// role?: 'admin' | 'user';
// }
// 常见用例:接受部分数据的更新函数
async function updateUser(id: number, updates: Partial<User>) {
return await db.users.update({ where: { id }, data: updates });
}
updateUser(1, { name: 'Alice' }); // ✅ 只更新 name
updateUser(1, { email: 'new@example.com' }); // ✅ 只更新 email
Required
与 Partial 相反——将所有可选属性变为必选。
interface Config {
host?: string;
port?: number;
database?: string;
}
type RequiredConfig = Required<Config>;
// { host: string; port: number; database: string; }
function connect(config: RequiredConfig) {
// 所有字段保证存在
}
Readonly
将所有属性变为初始化后不可赋值。
type ReadonlyUser = Readonly<User>;
const user: ReadonlyUser = { id: 1, name: 'Alice', email: 'a@b.com', role: 'user' };
user.name = 'Bob'; // ❌ TypeScript 错误:无法分配到 'name',因为它是只读属性
// 深度只读(Readonly 是浅层的)
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};
Pick<T, K>
从 T 中选取指定键创建类型。
type UserPreview = Pick<User, 'id' | 'name'>;
// { id: number; name: string; }
// 用例:仅暴露安全字段的 API 响应类型
type PublicUser = Pick<User, 'id' | 'name'>; // 从公共 API 中排除 email 和 role
function getUserProfile(id: number): Promise<PublicUser> {
return db.users.findOne({ where: { id }, select: ['id', 'name'] });
}
Omit<T, K>
从 T 中排除指定键创建类型——与 Pick 相反。
type UserWithoutId = Omit<User, 'id'>;
// { name: string; email: string; role: 'admin' | 'user'; }
// 用例:创建负载(在数据库分配 id 之前)
type CreateUserPayload = Omit<User, 'id'>;
async function createUser(payload: CreateUserPayload): Promise<User> {
return await db.users.create({ data: payload });
}
// 排除多个键
type UserFormData = Omit<User, 'id' | 'role'>;
Record<K, V>
创建键为 K、值为 V 的对象类型。
// 从字符串到数字的映射
type ScoreMap = Record<string, number>;
const scores: ScoreMap = { alice: 100, bob: 95, carol: 87 };
// 从联合类型到对象的映射
type UserRole = 'admin' | 'editor' | 'viewer';
type RolePermissions = Record<UserRole, { canEdit: boolean; canDelete: boolean }>;
const permissions: RolePermissions = {
admin: { canEdit: true, canDelete: true },
editor: { canEdit: true, canDelete: false },
viewer: { canEdit: false, canDelete: false },
};
// 类型安全的查找
function getPermissions(role: UserRole) {
return permissions[role]; // TypeScript 知道这个始终有值
}
Exclude<T, U>
从 T 中移除可赋值给 U 的类型。
type Status = 'active' | 'inactive' | 'deleted' | 'banned';
type ActiveStatus = Exclude<Status, 'deleted' | 'banned'>;
// 'active' | 'inactive'
// 常用于联合类型
type NonNullable<T> = Exclude<T, null | undefined>; // 这实际上是内置的!
type StringOrNull = string | null | undefined;
type JustString = Exclude<StringOrNull, null | undefined>; // string
Extract<T, U>
仅保留 T 中可赋值给 U 的类型(与 Exclude 相反)。
type Mixed = string | number | boolean | null;
type JustStrings = Extract<Mixed, string | number>; // string | number
type Primitives = Extract<Mixed, string | number | boolean>; // string | number | boolean
ReturnType
提取函数类型的返回类型。
function getUser() {
return { id: 1, name: 'Alice', email: 'alice@example.com' };
}
type GetUserReturn = ReturnType<typeof getUser>;
// { id: number; name: string; email: string; }
// 适用于异步函数(返回 Promise<T>)
async function fetchData() {
return { users: [] as User[], total: 0 };
}
type FetchDataReturn = Awaited<ReturnType<typeof fetchData>>;
// { users: User[], total: number }
Parameters
以元组形式提取函数参数类型。
function createPost(title: string, content: string, authorId: number) {
// ...
}
type CreatePostParams = Parameters<typeof createPost>;
// [title: string, content: string, authorId: number]
// 用于包装函数
function loggedCreatePost(...args: Parameters<typeof createPost>) {
console.log('Creating post:', args[0]);
return createPost(...args);
}
NonNullable
从 T 中移除 null 和 undefined。
type MaybeString = string | null | undefined;
type DefinitelyString = NonNullable<MaybeString>; // string
// 与 API 响应的实际用法
interface ApiResponse {
user: User | null;
}
function processUser(response: ApiResponse) {
if (response.user === null) return;
const user: NonNullable<typeof response.user> = response.user; // User (非 null)
}
组合实用类型
真正的威力来自于组合使用:
interface Product {
id: string;
name: string;
price: number;
description: string;
imageUrl: string;
createdAt: Date;
updatedAt: Date;
ownerId: string;
}
// 创建类型:无 id、时间戳或 ownerId(由服务器设置)
type CreateProductInput = Omit<Product, 'id' | 'createdAt' | 'updatedAt' | 'ownerId'>;
// 更新类型:与创建相同的字段,但全部可选
type UpdateProductInput = Partial<CreateProductInput>;
// 列表类型:仅预览字段
type ProductSummary = Pick<Product, 'id' | 'name' | 'price' | 'imageUrl'>;
// 管理员视图类型:全部只读
type AdminProduct = Readonly<Product>;
常见问题
问:Pick 和 Omit 有什么区别? Pick 白名单列出你想保留的字段。Omit 黑名单列出你想移除的字段。当你想要一个小子集时使用 Pick;当你想要除了少数几个字段之外的所有字段时使用 Omit。
问:什么时候应该使用 Partial 与可选属性?
当字段在基础类型中确实是可选时,使用可选属性(?)。当你需要一个通常全部必选的类型的全可选版本时(如更新负载),使用 Partial<T>。
问:Readonly 是深度只读的吗?
不,Readonly<T> 是浅层的。对于深度不可变类型,使用递归的 DeepReadonly 泛型或 as const 断言。
→ 使用 JSON Viewer 检查 TypeScript API 响应和类型结构。