You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 10:45:38