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

TypeScript中Readonly类型可赋值给可变类型的原因及强不可变性实现方案咨询

TypeScript中Readonly类型可赋值给可变类型的原因及强不可变性实现方案咨询

我完全懂你的困惑——本来以为用Readonly<Todo>标记的实例应该是完全不可修改的,结果却能通过赋值给普通Todo类型绕开限制,这确实有点违背我们对“不可变性”的预期。咱们先搞清楚为什么会这样,再看看怎么解决这个问题:

为什么Readonly<T>能赋值给可变类型?

TypeScript采用的是结构类型系统,简单来说就是只要两个类型的结构(属性、方法)一致,就认为它们是兼容的。Readonly<T>本质上只是给T的所有属性加上了readonly的类型标记,但并没有改变对象本身的结构——它是个编译时的类型约束,不是运行时的强制限制。所以TypeScript会认为Readonly<Todo>和Todo是兼容的,允许互相赋值,这就给了我们绕过只读限制的机会。

实现强不可变性的几种方案

如果想要真正锁住对象,不让它被修改,可以试试下面这些方法:

1. 从类设计根源上做不可变约束

直接把类的属性设为readonly,或者用私有属性+只读getter的方式,让实例在运行时也无法被修改:

class ImmutableTodo {
  constructor(
    private readonly _name: string,
    private readonly _active = true,
    private readonly _completed = false,
    private readonly _editing = false
  ) {}

  // 只提供只读的访问器,不暴露setter
  get name() { return this._name; }
  get active() { return this._active; }
  get completed() { return this._completed; }
  get editing() { return this._editing; }

  toString() { return this._name; }

  // 如果需要修改属性,返回一个新的实例(不可变数据模式)
  withName(newName: string): ImmutableTodo {
    return new ImmutableTodo(newName, this._active, this._completed, this._editing);
  }
}

这种方式下,不管是类型层面还是运行时,原有实例的属性都无法被修改,所有“修改”操作都会生成新对象,彻底保证不可变性。

2. 用自定义类型阻止只读类型向可变类型赋值

你可以定义一个Mutable工具类型,再配合类型守卫来限制赋值操作,虽然不能完全杜绝强制类型断言,但能在编译时增加一层约束:

// 移除类型的readonly修饰符
type Mutable<T> = { -readonly [K in keyof T]: T[K] };

// 自定义类型守卫,判断是否是可变类型实例
function isMutableTodo(t: Readonly<Todo> | Todo): t is Mutable<Todo> {
  // 这里可以根据实际需求加运行时检查,比如判断实例是否有特定标记
  return Object.prototype.hasOwnProperty.call(t, '_isMutable');
}

function f() {
  let ro: Readonly<Todo> = new Todo("bugs bunny");
  let t: Todo;
  
  if (isMutableTodo(ro)) {
    t = ro;
  } else {
    throw new Error("禁止将只读Todo实例赋值给可变类型");
  }
}

3. 使用不可变数据工具库(比如Immer)

如果你的场景是复杂状态管理,可以借助像Immer这样的库,它能帮你以“可变”的写法生成不可变的新对象,原对象始终保持不可变:

import { produce } from "immer";

let ro: Readonly<Todo> = new Todo("bugs bunny");
// 用produce生成新对象,原ro不会被修改
const updatedTodo = produce(ro, draft => {
  draft.name = "duffy duck";
});

总结

Readonly<T>更多是编译时的“提醒”,而非强制限制。如果想要真正的强不可变性,最好从类的设计入手(比如只读属性、不可变实例模式),或者借助工具库来实现。这样既能在编译时得到类型检查,也能在运行时保证对象不会被意外修改。

备注:内容来源于stack exchange,提问作者citykid

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.20 08:44:33