深度不可变模式
通过递归类型实现深度 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 反馈 — 无需本地设置。