开发者问题收集

无法分配给 TypeScript 中对象的只读属性

2022-05-08
5759

有人能向我解释一下为什么以及如何发生这种情况吗?

我有一个带有 Zustand 状态管理的 TypeScript 应用程序。

在应用程序内部的某个地方,我正在通过从状态中提取某些元素并通过简单的 Object.Assign 克隆它们来更新它们:

let elemToUpdate = Object.assign({},story?.content?.elementsData[nodeId]);

if (elemToUpdate) {

    if (elemToUpdate.title) elemToUpdate.title[editorLang] = newName;

    else elemToUpdate.title = {[editorLang]:newName} as TextDictionary;

    updateElement(nodeId,elemToUpdate);
}

现在有趣的部分是:在我第一次尝试时,更新顺利完成,但我尝试更新的下一个对象失败并显示以下消息:

Tree.tsx:39 Uncaught TypeError: Cannotassign to read only property 'en' of object '#<Object>'

我不明白为什么第一个通过了,但第二个被阻止了。

我知道如何解决这个问题:我需要做一个深度克隆。我只是想了解为什么。

2个回答

首先 ,让我们从为什么代码中的某些对象是 只读 开始。根据您在问题中描述的内容,您使用了一个 Zustand 状态管理器。此类管理器传统上将您存储的数据包装为只读对象,以防止其手动变异(期望您仅通过内置机制更改状态)以保证数据稳定性。因此,如果 story?.content?.elementsData[nodeId] 是 Zustand 状态对象,则它及其所有嵌套对象都将转换为只读。

其次 ,让我们定义哪些对象将被阻止。我在这里看到至少两个对象: elemToUpdate:{...,title:{[lang]:string} } (即 elemToUpdate 及其 title )。两者都将转换为只读。

第三 ,您使用 Object.assign({}, ...) 创建一个新对象(新引用)并克隆源对象的所有属性。请注意,这仅发生在第一级属性中,它不是深度克隆。因此,由于 title 是对另一个对象的引用,它将按原样克隆,并且在新对象中它仍将导致现有的 { [lang]: string } 对象。有几种方法可以解决这个问题:

  1. 深度克隆,正如您所提到的
  2. 手动克隆 title 属性,例如 {..., title: { ... elemToUpdate.title } } 或通过 Object.assign

但我不建议您以这种方式改变对象。很可能您的整个算法总体上存在一些架构问题。

Roman Maksimov
2022-05-26

这是预料之中的,因为在第一种情况下,您没有为 title 属性分配新值:您只是在更改 title 属性 内部 的值。在第二种情况下,您将重新分配 title 属性的整个值。

您无法更改 readonly 属性。让我们通过一个简单的示例来理解它:

Javascript: 仅用于与问题无关的示例

const user = { // a const is immutable
    name: 'John',
}
user.name = 'Pete'; // This works


const user = {
    name: 'John'
}
user = { name: 'Pete'} // This doesn't work

Typescript:

const user: Readonly<{
   a: {
        name: string
   }
}> = {
  a:{ name: 'John' }
}

user.a.name = 'Pete'; // This works
user.a = { name: 'John' } // Does not work

您的示例中也发生了同样的情况:Typescript 不会检查深度 Readonly 属性。请查看 此处

Rahul Sharma
2022-05-23