Pick 是 TypeScript 中的一个实用类型,用于从已有的类型中选择一组属性,生成一个新的类型。它可以帮助我们在代码中更精细地控制类型,从而提高代码的类型安全性和可维护性。
基本使用
假设我们有一个 User 类型:
ts
interface User {
id: number;
name: string;
email: string;
age: number;
}
我们想创建一个新的类型,只包含 name 和 email 属性。我们可以使用 Pick 来实现:
ts
type UserContactInfo = Pick<User, "name" | "email">;
const contactInfo: UserContactInfo = {
name: "Moment",
email: "moment@qq.com",
};
实际的应用
Pick 在实际开发中非常有用,尤其是在处理大型接口或模型时。常见的应用场景包括:
- 选择性暴露接口,在 API 返回值或前端组件中,只暴露必要的属性:
ts
type PublicUserInfo = Pick<User, "name" | "email">;
- 简化类型定义,在处理复杂的类型或接口时,可以简化类型定义,避免重复代码:
ts
interface CreateUserRequest extends Pick<User, "name" | "email" | "age"> {}
- 类型约束,在函数参数中,只允许特定的属性:
ts
function sendEmail(user: Pick<User, "email">) {}
实现原理
Pick 类型的实现原理如下所示:
ts
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
在上面的代码中:
-
T 是源类型。
-
K 是一个属性名的联合类型,表示要从 T 中选取的属性。
-
P in K
表示循环 K 中的每个属性 P,并将其添加到新的类型中。 -
T[P]
表示 T 类型中属性 P 的类型。
进阶使用
结合泛型函数使用
我们可以定义一个泛型函数,使用 Pick 来选择参数类型的部分属性:
ts
interface User {
id: number;
name: string;
email: string;
age: number;
address: string;
}
function getUserInfo<T extends keyof User>(
user: User,
keys: T[]
): Pick<User, T> {
const result = {} as Pick<User, T>;
keys.forEach((key) => {
result[key] = user[key];
});
return result;
}
const user: User = {
id: 1,
name: "Moment",
email: "moment@qq.com",
age: 18,
address: "西安",
};
const userInfo = getUserInfo(user, ["name", "email"]);
console.log(userInfo);
最终输出结果如下所示:
动态选择属性
通过结合条件类型、映射类型、索引类型查询、索引访问类型和工具类型,实现查找出接口中的可选类型:
ts
interface User {
id: number;
name: string;
email: string;
age: number;
address?: string;
}
// 判断属性是否为可选
type IsOptional<T, K extends keyof T> = {} extends Pick<T, K> ? K : never;
// 提取可选属性的键,并排除掉 undefined 类型
type OptionalKeys<T> = Exclude<
{
[K in keyof T]: IsOptional<T, K>;
}[keyof T],
undefined
>;
// 使用 Pick 提取可选属性的类型
type UserOptionalProperties = Pick<User, OptionalKeys<User>>;
const userOptionalProps: UserOptionalProperties = {
address: "广州",
};
console.log(userOptionalProps);
在上面的代码中,条件类型允许我们根据类型的特性来选择不同的类型。条件类型的基本形式是 T extends U ? X : Y
,意思是如果类型 T 能够赋值给类型 U,则结果是类型 X,否则是类型 Y。
ts
type IsOptional<T, K extends keyof T> = {} extends Pick<T, K> ? K : never;
这里 {} extends Pick<T, K>
检查 {}
是否能赋值给 Pick<T, K>
,即属性 K 是否可选。如果可选,结果为 K,否则为 never。
深层属性选择
使用递归类型,可以实现从嵌套对象中选择属性。
ts
interface Address {
street: string;
city: string;
}
interface User {
id: number;
name: string;
email: string;
age: number;
address: Address;
}
// 递归地选择深层属性
type DeepPick<T, K extends keyof T> = {
[P in K]: T[P] extends object ? DeepPick<T[P], keyof T[P]> : T[P];
};
type UserAddressOnly = DeepPick<User, "address">;
const example: UserAddressOnly = {
address: {
street: "中山大道",
city: "广州",
},
};
条件类型与 Pick 结合使用
我们可以使用条件类型来选择符合特定条件的属性:
ts
interface User {
id: number;
name: string;
email: string;
age: number;
isActive: boolean;
}
// 选择布尔类型的属性
type BooleanProperties<T> = {
[K in keyof T]: T[K] extends boolean ? K : never;
}[keyof T];
type UserBooleanProperties = Pick<User, BooleanProperties<User>>;
const boo: UserBooleanProperties = {
isActive: false,
};
最终类型如下图所示:
结合 Exclude 使用
我们可以结合 Pick 和 Exclude 来实现复杂的类型选择:
ts
interface User {
id: number;
name: string;
email: string;
age: number;
address: string;
isActive: boolean;
}
// 排除特定类型的属性
type ExcludeProperties<T, U> = {
[K in keyof T]: T[K] extends U ? never : K;
}[keyof T];
type NonStringProperties<T> = Pick<T, ExcludeProperties<T, string>>;
type UserNonStringProperties = NonStringProperties<User>;
const user: UserNonStringProperties = {
age: 18,
id: 7,
isActive: true,
};
最终类型如下图所示:
总结
Pick 是 TypeScript 中一个强大的实用类型,允许你从现有类型中选择特定的属性,创建新的类型。
通过这些高级用法示例,我们可以看到 Pick 类型在 TypeScript 中的强大和灵活性。合理使用这些技术可以帮助你编写更健壮和可维护的类型安全代码。