Skip to content

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. 工具类型实现:PickReadonly

实现 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 ] 的数组

知识回顾

  1. keyof 运算符
    • 获取对象类型的所有键,返回联合类型。
    • 联合类型取键的交集,交叉类型取键的并集
  2. interface 定义对象
    • 支持必填、可选、只读属性,可继承扩展。
  3. 键值对语法
    • T[K] 动态获取属性值类型,结合泛型实现精准约束。
  4. 类型体操
    • 通过泛型和条件类型实现复杂类型验证与转换。

课后练习

  1. (单选)以下 keyof 的返回类型是?

    typescript
    type T = { a: 1; b: 2 };
    type Keys = keyof T;
    • A. "a"
    • B. "a" | "b"
    • C. 1 | 2
    • D. number
  2. (填空)定义一个 PartialAdmin 类型,将 Admin 接口的属性全部设为可选。

    typescript
    interface Admin { id: number; role: "admin"; }
    type PartialAdmin = ___<Admin>; // 填写工具类型
  3. (编码)实现一个 KeysToOptional<T> 工具类型,将对象类型的所有属性设为可选。

    typescript
    type KeysToOptional<T> = { [P in ___<T>]? : T[P] };

扩展阅读

Built by Vitepress | Apache 2.0 Licensed