文章目录

TypeScript映射类型实现DeepReadonly的递归处理

发布于 2026-05-09 15:21:07 · 浏览 15 次 · 评论 0 条

TypeScript映射类型实现DeepReadonly的递归处理

1. 基础概念

TypeScript中的映射类型是一种强大的工具,允许我们基于现有类型创建新类型。Readonly是TypeScript内置的映射类型,用于将对象的属性标记为只读。

interface Person {
  name: string;
  age: number;
}

type ReadonlyPerson = Readonly<Person>;

// 等价于
interface ReadonlyPerson {
  readonly name: string;
  readonly age: number;
}

Readonly类型将对象的所有属性标记为只读,但不会递归处理嵌套对象。这意味着嵌套对象的属性仍然可以被修改。

const person: ReadonlyPerson = {
  name: "Alice",
  age: 30,
  address: {
    city: "Beijing",
    street: "Main St"
  }
};

// 错误:无法为只读属性赋值
person.name = "Bob"; // Error: Cannot assign to 'name' because it is a read-only property.

// 但可以修改嵌套对象的属性
person.address.city = "Shanghai"; // 这是允许的

2. 递归处理需求

为了实现真正的深度只读(DeepReadonly),我们需要递归地处理对象的每个属性。如果属性是对象,我们需要将其也转换为只读。这需要使用条件类型和递归。

3. 实现DeepReadonly

我们可以通过以下步骤实现DeepReadonly:

  1. 定义基本类型:创建一个类型别名DeepReadonly
  2. 使用条件类型:检查类型是否为对象。
  3. 递归处理:如果是对象,则递归应用DeepReadonly。
  4. 处理其他类型:如果不是对象,则保持原样。
type DeepReadonly<T> = {
  readonly [P in keyof T]: T[P] extends object 
    ? T[P] extends Function 
      ? T[P] 
      : DeepReadonly<T[P]>
    : T[P];
};

代码解析

  • keyof T:获取类型T的所有键。
  • readonly [P in keyof T]:将每个属性标记为只读。
  • T[P] extends object:检查属性是否为对象。
  • T[P] extends Function:检查属性是否为函数(函数不需要递归处理)。
  • DeepReadonly<T[P]>:递归应用DeepReadonly。

4. 使用示例

让我们看看DeepReadonly如何工作:

interface Person {
  name: string;
  age: number;
  address: {
    city: string;
    street: string;
  };
  hobbies: string[];
}

type ReadonlyPerson = DeepReadonly<Person>;

const person: ReadonlyPerson = {
  name: "Alice",
  age: 30,
  address: {
    city: "Beijing",
    street: "Main St"
  },
  hobbies: ["reading", "swimming"]
};

// 错误:无法修改任何属性
person.name = "Bob"; // Error
person.address.city = "Shanghai"; // Error
person.hobbies.push("coding"); // Error

5. 处理数组

DeepReadonly也能正确处理数组,因为数组在TypeScript中是对象:

interface Data {
  items: string[];
}

type ReadonlyData = DeepReadonly<Data>;

const data: ReadonlyData = {
  items: ["item1", "item2"]
};

// 错误:无法修改数组
data.items.push("item3"); // Error

6. 处理函数

函数在DeepReadonly中保持不变,因为函数不需要被标记为只读:

interface Handler {
  onClick: (event: Event) => void;
}

type ReadonlyHandler = DeepReadonly<Handler>;

const handler: ReadonlyHandler = {
  onClick: (event) => console.log(event)
};

// 函数仍然可以调用
handler.onClick({ type: "click" });

7. 完整实现与测试

以下是完整的DeepReadonly实现和测试用例:

// DeepReadonly实现
type DeepReadonly<T> = {
  readonly [P in keyof T]: T[P] extends object 
    ? T[P] extends Function 
      ? T[P] 
      : DeepReadonly<T[P]>
    : T[P];
};

// 测试用例
interface ComplexObject {
  id: number;
  name: string;
  data: {
    value: number;
    details: {
      description: string;
    };
  };
  items: string[];
  handler: (arg: string) => void;
}

type ReadonlyComplexObject = DeepReadonly<ComplexObject>;

const obj: ReadonlyComplexObject = {
  id: 1,
  name: "Test",
  data: {
    value: 42,
    details: {
      description: "Deep nested object"
    }
  },
  items: ["item1", "item2"],
  handler: (arg) => console.log(arg)
};

// 测试修改操作
obj.id = 2; // Error
obj.data.value = 100; // Error
obj.data.details.description = "Modified"; // Error
obj.items.push("item3"); // Error
obj.handler = () => {}; // Error

8. 注意事项

  1. 循环引用:DeepReadonly不能处理包含循环引用的对象,会导致无限递归。
  2. Symbol属性:DeepReadonly会处理Symbol属性,因为keyof包括Symbol键。
  3. 只读数组:DeepReadonly不会使数组本身只读,只是其元素。要使数组只读,需要额外处理。
type ReadonlyArray<T> = readonly T[];
  1. 性能考虑:对于非常深的嵌套对象,递归可能影响编译性能。

9. 扩展实现

我们可以扩展DeepReadonly以处理更多情况,比如只读数组:

type DeepReadonly<T> = T extends (infer R)[] 
  ? readonly R[] 
  : T extends Function 
    ? T 
    : T extends object 
      ? { readonly [P in keyof T]: DeepReadonly<T[P]> }
      : T;

这个实现首先检查类型是否为数组,如果是,则将其标记为只读数组。然后检查是否为函数,最后处理对象。

10. 实际应用场景

DeepReadonly在以下场景中特别有用:

  1. 状态管理:在Redux或React Context中,确保状态对象不被意外修改。
  2. 配置对象:确保配置对象在运行时不会被修改。
  3. API响应:确保从API获取的数据在应用中不被修改。
// Redux状态示例
type State = {
  user: {
    id: number;
    name: string;
    preferences: {
      theme: "light" | "dark";
    };
  };
  settings: {
    notifications: boolean;
  };
};

type ReadonlyState = DeepReadonly<State>;

const state: ReadonlyState = {
  user: {
    id: 1,
    name: "John",
    preferences: {
      theme: "light"
    }
  },
  settings: {
    notifications: true
  }
};

// 确保状态不会被修改
state.user.name = "Jane"; // Error
state.settings.notifications = false; // Error

通过实现DeepReadonly,我们可以确保对象的深度不可变性,提高代码的可靠性和可维护性。

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文