TypeScript如何防止只读属性被绕过编译检查非法赋值
问题本质
TypeScript 中readonly修饰符默认仅做浅度的协变检查:带readonly标记的属性类型,会被判定为对应可变属性类型的子类型,因此可以合法传入要求可变属性的函数参数。这个设计是为了适配JavaScript无原生只读约束、对象传递灵活的特性,默认不会拦截“只读值传入可变参数位”的写法,才会出现示例中绕过编译检查、意外修改只读字段的问题。
编译阻断方案
方案1:原生TS类型品牌(Branded Type)实现,无运行时开销
通过编译期唯一标记区分可变/只读类型,不需要额外依赖,编译后不会产生任何额外JS代码,改造示例如下:
// 仅编译期存在的唯一标记,无运行时成本 declare const MUTABLE: unique symbol; // 标记可变类型,普通对象无标记时默认符合可变要求 type Mutable<T> = T & { [MUTABLE]?: true }; // 标记只读/不可变类型,自动给属性加readonly修饰和互斥标记 type Immutable<T> = { readonly [K in keyof T]: T[K]; } & { readonly [MUTABLE]?: false }; // 用Immutable统一生成只读类型 type Ro = Immutable<{ x: string; }> // 入参要求必须是可变类型 const modify = (rw: Mutable<{x: string;}>) => rw.x = 'bye'; const use = (v: Ro) => { console.log(v.x); //v.x = 'bye'; 原有只读赋值报错保留 modify(v);// 此处直接触发编译错误:Ro的可变标记为false,无法匹配Mutable要求 } const ro: Ro = {x: "hi"} use(ro); // 普通可变对象可正常传入,无需额外添加字段 const normalObj = {x: "tmp"}; modify(normalObj); // 编译通过
项目中只要统一用Immutable生成只读类型、用Mutable包装需要可变入参的位置,就可以完全阻断这类非法传参。
方案2:ESLint规则检查,零业务代码侵入
如果不想改造现有类型定义,可以基于TS类型检查的ESLint规则拦截这类问题:
- 配置项目支持
@typescript-eslint解析器和对应插件 - 开启只读/可变传参的相关检查规则,在编码、编译阶段直接提示只读类型传入可变参数的错误,不需要修改任何业务代码逻辑。
目前TypeScript官方正在推进原生精确类型(Exact Types)特性,未来稳定版会原生支持严格的属性可变性检查,不需要额外包装或工具即可拦截这类问题。
内容的提问来源于stack exchange,提问作者Pavel Husakouski
相关产品推荐
相关产品推荐

