TypeScript readonly 详解:编译期不可变性

readonly 在编译期防止属性与数组被修改。本文讲解 readonly 属性、ReadonlyArray、与 const/Object.freeze 的三角关系。

最佳实践
类型结构与组件组合插画

readonly 是编译期约束:readonly name: string 的属性不能重新赋值,readonly string[] 的数组不能 push。与 const(防变量重绑)和 Object.freeze(运行时浅冻结)互补——三者各管一层。

可变性问题

function processUsers(users: { name: string }[]) {
  users[0].name = '篡改';   // 调用方的数据被悄悄改了
}

函数修改传入对象是隐蔽 bug 的温床——readonly 把"不许改"变成编译期契约。

readonly 属性

interface Config {
  readonly apiUrl: string;
  readonly timeout: number;
}

const config: Config = { apiUrl: 'https://api.example.com', timeout: 5000 };
config.timeout = 3000;   // 编译错误

Readonly<T> 工具类型一键全只读:Readonly<User>。

只读数组与元组

const list: readonly string[] = ['a', 'b'];
list.push('c');      // 编译错误
list[0] = 'x';       // 编译错误

// ReadonlyArray<string> 等价
function sum(nums: readonly number[]) { /* 保证不改传入数组 */ }

函数签名用 readonly 数组是善意声明:我不动你的数据——调用方传数据时无后顾之忧。

readonly vs const vs Object.freeze

const readonly Object.freeze
管什么 变量重绑 属性/元素修改 运行时浅冻结
时机 编译期 编译期 运行时
const obj = { a: 1 } as const;   // as const = readonly + 字面量化
obj.a = 2;  // 编译错误

as const 是 readonly 的激进形态:全属性只读 + 字面量类型。

边界

readonly 是编译期约束——运行时数据照样能被 JS 改。跨边界(序列化进出)后约束消失。需要运行时保障的场景配合 Object.freeze(注意:浅冻结,嵌套对象要深冻结库)。

常见问题(FAQ)

Q:readonly 数组能转回可变数组吗?

需要显式拷贝:[...readonlyArr]。类型系统故意制造摩擦——转换点即审计点。

Q:React state 该用 readonly 吗?

推荐——state 不可变是 React 的心智模型,Readonly props 类型让"不许直接改 props"从约定变为编译强制。

Q:深只读怎么做?

手写递归类型(DeepReadonly,见本系列《映射类型》)或用 ts-essentials 库的现成实现。


参考文档

本文依据公开文档整理,示例需按项目依赖版本验证;不将文档中的性能描述视为本项目实测结果。

延伸阅读

获取专属方案

联系我们

加入社区

微信扫码
加入官方交流群

立即体验

在线开通,按量计费,真正的云服务!

立即开始

选择观测云版本

代码托管平台