D22. 定义复杂类型
2.1. 🌟 keyof
运算符获得对象所有键
核心思想: keyof
运算符可获取对象类型的 所有键,返回一个由键名组成的 联合类型。它适用于接口、类、对象类型等,是类型安全操作的基础。
2.1.1. 基础用法与示例
获取接口/类的键
typescript
interface User {
name: string;
age: number;
isAdmin: boolean;
}
type UserKeys = keyof User; // "name" | "age" | "isAdmin"
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key]; // 类型安全,确保 key 存在
}
const user = { name: "Alice", age: 25, isAdmin: false };
const name = getProperty(user, "name"); // 类型推断为 string
// const invalid = getProperty(user, "address"); // ❌ 错误:键不存在
实现类型安全的索引访问 通过 keyof
约束泛型参数,避免使用 any
:
typescript
// 错误示例:使用 any 可能导致类型不安全
function unsafeGetProperty(obj: any, key: any) {
return obj[key]; // 可能返回任意类型
}
// 正确示例:类型安全且精准
const age = getProperty(user, "age"); // 类型推断为 number
2.1.2. 特殊场景与注意事项
联合类型的键取交集
typescript
type A = { a: string; common: number };
type B = { b: string; common: number };
type KeysOfUnion = keyof (A | B); // "common"(取交集)
交叉类型的键取并集
typescript
type A = { a: string; shared: number };
type B = { b: string; shared: number };
type C = A & B;
type KeysOfIntersection = keyof C; // "a" | "b" | "shared"
数组的键包含索引和方法
typescript
type ArrayKeys = keyof string[]; // "length" | "toString" | "join" | ... | number
// 数组的索引(0、1、2 等)会被转为字符串形式
注意
若对象的键名是数字或特殊字符,需用引号包裹:
typescript
const obj = { "123": "value", specialKey: "test" };
type ObjKeys = keyof typeof obj; // "123" | "specialKey"
2.2. 🌟 interface
语句定义常见的对象类型
核心思想: interface
是 TypeScript 定义对象结构的核心工具,可声明 必填属性、可选属性、只读属性,并支持继承和扩展。
2.2.1. 基础定义与扩展
必填属性
typescript
interface User {
name: string; // 必填
age: number; // 必填
}
const user: User = { name: "Bob" }; // ❌ 错误:age 缺失
可选属性
typescript
interface User {
name: string;
age?: number; // 可选属性
}
const user: User = { name: "Charlie" }; // 合法
只读属性
typescript
interface Config {
readonly host: string; // 只读属性
port: number;
}
const config: Config = { host: "localhost", port: 3000 };
config.host = "remote"; // ❌ 错误:不能修改只读属性
2.2.2. 继承与混合类型
继承接口
typescript
interface Admin extends User {
role: "admin"; // 新增必填属性
}
const admin: Admin = {
name: "Diana",
role: "admin",
age: 30 // 可选属性可保留
};
可选 + 只读组合
typescript
interface ReadOnlyUser {
readonly id: number; // 只读且必填
name?: string; // 可选且只读?
}
// ❗ 注意:TypeScript 无“只读可选”语法,需用类型断言处理
2.3. 🌟 keyof
衍生的键值对定义语法
核心思想: 结合 keyof
和 索引访问类型(T[K]
),可实现 键值对的精准类型约束,常见于泛型函数或工具类型。
2.3.1. 索引访问类型 T[K]
示例:动态获取属性值类型
typescript
interface Product {
id: number;
name: string;
price: number;
}
type ProductId = Product["id"]; // number
type ProductNameOrPrice = Product["name" | "price"]; // string | number
泛型函数中的应用
typescript
function getValue<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const price = getValue<Product, "price">(product, "price"); // 类型为 number
2.3.2. 工具类型实现:Pick
和 Readonly
实现 Pick
typescript
type Pick<T, K extends keyof T> = {
[P in K]: T[P]; // 遍历 K 中的每个键,复制类型
};
type ProductDetails = Pick<Product, "name" | "price">;
// 等价于 { name: string; price: number; }
实现 Readonly
typescript
type Readonly<T> = {
readonly [P in keyof T]: T[P]; // 所有属性标记为只读
};
type ReadOnlyUser = Readonly<User>; // 所有属性变为只读
2.4. 🌟 keyof
相关的类型体操
核心思想: 结合 keyof
、泛型、条件类型等,实现 动态类型验证、结构转换 等复杂操作。
2.4.1. 验证对象是否符合键约束
确保对象包含所有必需键
typescript
type RequiredKeys<T> = { [K in keyof T]-?: unknown }; // 强制必填
type Validate<T, U> = T extends RequiredKeys<U> ? true : false;
interface RequiredUser {
name: string;
age: number;
}
type Check1 = Validate<{ name: string }, RequiredUser>; // false(缺少 age)
type Check2 = Validate<{ name: string; age: number }, RequiredUser>; // true
2.4.2. 动态提取键值类型
提取函数返回值的类型
typescript
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
function getUser(): { name: string; id: number } {
return { name: "Eve", id: 1 };
}
type UserReturnType = ReturnType<typeof getUser>; // { name: string; id: number }
2.4.3. 构建键值对映射类型
将键转为 key-value
对数组
typescript
type Entries<T> = {
[K in keyof T]: [K, T[K]];
}[keyof T][]; // 联合类型转数组
type ProductEntries = Entries<Product>; // [ "id", number ] | [ "name", string ] 的数组
知识回顾
keyof
运算符- 获取对象类型的所有键,返回联合类型。
- 联合类型取键的交集,交叉类型取键的并集。
interface
定义对象- 支持必填、可选、只读属性,可继承扩展。
- 键值对语法
T[K]
动态获取属性值类型,结合泛型实现精准约束。
- 类型体操
- 通过泛型和条件类型实现复杂类型验证与转换。
课后练习
(单选)以下
keyof
的返回类型是?typescripttype T = { a: 1; b: 2 }; type Keys = keyof T;
- A.
"a"
- B.
"a" | "b"
- C.
1 | 2
- D.
number
- A.
(填空)定义一个
PartialAdmin
类型,将Admin
接口的属性全部设为可选。typescriptinterface Admin { id: number; role: "admin"; } type PartialAdmin = ___<Admin>; // 填写工具类型
(编码)实现一个
KeysToOptional<T>
工具类型,将对象类型的所有属性设为可选。typescripttype KeysToOptional<T> = { [P in ___<T>]? : T[P] };
扩展阅读