0Pricing
TypeScript Academy · 课时

深度不可变模式

通过递归类型实现深度 readonly 结构。

深度不可变模式 是 CoddyKit 上的免费 TypeScript Academy 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 TypeScript Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 TypeScript Academy 课程共包含 4 节课。

浅层与深层不可变性

readonly 修饰符是浅层的:它保护顶层,但不保护嵌套对象。深层不可变性意味着每一层一直到最底层都只读。要实现这一点需要额外的工作。

interface State { readonly user: { name: string }; }
const s: State = { user: { name: 'Ada' } };
// s.user = {...}; // Error (shallow protection)
s.user.name = 'Grace'; // allowed! nested field is mutable
console.log(s.user.name);

为什么浅层不可变性还不够

对于状态管理和共享数据,浅层只读仍会暴露嵌套字段。结构深处一次被忽略的修改,就可能导致细微且难以追踪的错误。

interface Config { readonly db: { host: string; port: number }; }
const c: Config = { db: { host: 'localhost', port: 5432 } };
c.db.port = 9999; // mutates nested data despite readonly
console.log(c.db.port);

递归的 DeepReadonly 类型

我们可以构建一种映射类型,递归地应用 readonly。DeepReadonly<T> 会遍历每个属性;如果属性是对象,就继续递归处理。

type DeepReadonly<T> = {
  readonly [K in keyof T]: T[K] extends object
    ? DeepReadonly<T[K]>
    : T[K];
};
console.log('DeepReadonly defined');

应用 DeepReadonly

用 DeepReadonly 包装一个类型,也会使嵌套属性不可变。现在,即使是 s.user.name 也不能重新赋值——保护会一直深入到底层。

type DeepReadonly<T> = {
  readonly [K in keyof T]: T[K] extends object
    ? DeepReadonly<T[K]>
    : T[K];
};
type State = DeepReadonly<{ user: { name: string } }>;
const s: State = { user: { name: 'Ada' } };
// s.user.name = 'x'; // Error now
console.log(s.user.name);

映射类型的工作原理

[K in keyof T] 会遍历每个键。readonly 前缀会锁定每个键。条件类型表达式 T[K] extends object ? ... : T[K] 会决定是继续递归,还是在原始类型处停止。

type DeepReadonly<T> = {
  readonly [K in keyof T]: T[K] extends object
    ? DeepReadonly<T[K]>
    : T[K];
};
type Cfg = DeepReadonly<{ a: number; b: { c: string } }>;
const cfg: Cfg = { a: 1, b: { c: 'x' } };
console.log(cfg.a, cfg.b.c);

仅在编译时提供保护

DeepReadonly 在类型系统中强制执行不可变性,而不是在运行时执行。JavaScript 对象在技术上仍然可以通过无类型路径进行修改,但您的类型化代码会得到完整保护。

// Type-level immutability does not freeze the runtime object.
// For runtime guarantees, combine it with Object.freeze.
console.log('Types guard your code; freeze guards runtime');

在运行时冻结嵌套对象

如果需要在运行时实现真正的不可变性,请使用 Object.freeze。它可以阻止添加、删除或修改属性,但与 readonly 一样,默认情况下是浅层的。

const config = Object.freeze({ host: 'localhost', port: 8080 });
console.log(config.port);
// In strict mode, config.port = 1 throws; otherwise silently ignored.

深度冻结辅助函数

要在运行时进行深度冻结,请递归遍历嵌套对象并冻结每个对象。它与类型层面的 DeepReadonly 配合使用,可以自然地实现端到端的不可变性。

function deepFreeze<T>(obj: T): T {
  Object.values(obj as any).forEach((v) => {
    if (v && typeof v === 'object') deepFreeze(v);
  });
  return Object.freeze(obj);
}
const frozen = deepFreeze({ a: { b: 1 } });
console.log(frozen.a.b);

不可变性让状态更安全

深层不可变性是状态可预测性的基础。当数据不能被原地修改时,更新就必须创建新对象,从而使变更明确且易于追踪。

const state = { count: 0, user: { name: 'Ada' } };
// Immutable update: create a new object instead of mutating
const next = { ...state, count: state.count + 1 };
console.log(state.count, next.count);

不可变更新模式

对于只读数据,您可以通过展开运算生成新版本。每次更新都会创建一个新对象,因此之前的状态仍然保持不变——这对撤销、时间旅行调试和变更检测非常有价值。

type State = { readonly items: readonly string[] };
const s: State = { items: ['a'] };
const updated: State = { items: [...s.items, 'b'] };
console.log(s.items, updated.items);

结合类型安全与运行时安全

最稳妥的方法是同时使用两者:用 DeepReadonly 提供编译时保证,用 deepFreeze 在运行时强制执行。两者结合后,意外修改几乎不可能发生。

function freeze<T>(obj: T): T {
  return Object.freeze(obj);
}
const settings = freeze({ theme: 'dark', version: 2 });
console.log(settings.theme, settings.version);

快速检查

检验您对深层不可变性的理解。

回顾:深层不可变性

您已了解到:

  • readonly 是浅层的;嵌套字段仍然可变。
  • 递归的 DeepReadonly<T> 映射类型会在每一层应用只读属性。
  • 它仅在编译时生效;如需在运行时强制执行,请使用 Object.freeze(进行深度冻结)。
  • 不可变性通过更新时复制的模式,实现安全且可预测的状态管理。

至此,TypeScript 学院的高级类型与不可变性学习路线就全部完成了。

type State = { readonly value: number };
const s: State = { value: 1 };
const next: State = { ...s, value: s.value + 1 };
console.log(s.value, next.value);

常见问题解答

「深度不可变模式」课时是免费的吗?

是的 — 「深度不可变模式」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 TypeScript Academy 课程的其余内容,请升级到 CoddyKit PRO。 TypeScript Academy 课程共包含 4 节课。

「深度不可变模式」这节课中我会学到什么?

通过递归类型实现深度 readonly 结构。 你通过在浏览器中直接运行的动手代码来练习 TypeScript Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 TypeScript Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 TypeScript Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。

「深度不可变模式」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 TypeScript Academy 课中编写并运行代码吗?

能。每节 TypeScript Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. readonly 属性
  2. readonly 数组与元组
  3. ReadonlyArray 与 ReadonlyMap
  4. 深度不可变模式
← 返回 TypeScript Academy