我已经使用React.js进行了几个项目。他们中的一些人使用了Flux,一些人使用Redux,还有一些只是使用Context的普通React应用。
我真的很喜欢Redux使用功能模式的方式。但是,开发人员很可能无意间改变了状态。在寻找解决方案时,基本上只有一个答案-Immutable.js。老实说,我讨厌这个图书馆。它完全改变了您使用JavaScript的方式。而且,它必须在整个应用程序中实现,否则当某些对象是纯JS而某些对象是不可变结构时,您最终会遇到奇怪的错误。或者您开始使用.toJS()
,这-再次-非常非常糟糕。
最近,我的一位同事建议使用TypeScript。除了类型安全之外,它还具有一个有趣的功能-您可以定义自己的数据结构,其所有字段都标记为readonly
。这样的结构本质上是不可变的。
我不是Immutable.js或TypeScript的专家。但是,在Redux存储区中拥有不可变数据结构且不使用Immutable.js的承诺似乎太过真实了。 TypeScript的readonly
是否可以代替Immutable.js?还是有任何隐藏的问题?
答案 0 :(得分:3)
虽然确实是TypeScript的readonly
修饰符仅存在于设计类型,并且不影响运行时代码,但对于整个类型系统都是如此。也就是说,在运行时没有什么能阻止您将数字分配给类型为string
的变量。因此,这个答案有点像是一条红鲱鱼……如果您在设计时被警告要尝试更改标记为const
或readonly
的内容,则可能无需进行广泛的设计运行时检查。
但是,readonly
不足的主要原因是主要原因。有一个readonly
和一个outstanding issue,这表示当前(从TS3.4开始),仅在其readonly
属性上不同的类型是可以相互分配的。这样一来,您就可以轻松地破坏任何财产的保护性readonly
外壳并与内脏混在一起:
type Person = { name: string, age: number }
type ReadonlyPerson = Readonly<Person>;
const readonlyPerson: ReadonlyPerson = { name: "Peter Pan", age: 12 };
readonlyPerson.age = 40; // error, "I won't grow up!"
const writablePerson: Person = readonlyPerson; // no error?!?!
writablePerson.age = 40; // no error! Get a job, Peter.
console.log(readonlyPerson.age); // 40
这对readonly
来说是非常糟糕的。在此问题得到解决之前,您可能会发现自己与以前的问题归档者(他有originally named the issue“只读修饰符是个玩笑”)达成共识。
即使此问题得到解决,readonly
也可能无法涵盖所有用例。您还需要遍历库(甚至是标准库)中的所有接口和类型,并删除会改变状态的方法。因此,Array
的所有用法都需要更改为ReadonlyArray
,Map
的所有用法都需要更改为ReadonlyMap
,依此类推。完成此操作后,您将有一个相当类型安全的方式来表示不变性。但这是很多工作。
无论如何,希望能有所帮助;祝你好运!
答案 1 :(得分:1)
这有两个问题:
1)您必须一直使用readonly
和/或ReadonlyArray
之类的东西,这容易出错。
2)readonly
仅在编译时存在,而不在运行时存在,除非有不可变的数据存储支持。将代码转换为JS后,您的 runtime 代码即可执行所需的任何操作。
答案 2 :(得分:1)
Immutable.js
的目的不是为了防止开发人员在编译时进行非法更改。它提供了一种方便的API,可用于创建对象的副本并更改其某些属性。在使用immutable.js管理的对象上获得类型安全的事实基本上只是使用它的副作用。
打字稿“只是”打字系统。它没有实现Immutable.js
用来复制不可变对象的任何功能。当将变量声明为readonly
时,它所做的只是在编译时检查您是否对其进行了突变。设计代码以处理不变性的方法不是打字系统的范围,您仍然需要一种处理它的方法。
React通过提供方法setState
而不是直接改变状态对象来确保不变性。它会为您合并更改后的属性。但是如果你使用redux,您可能还需要一个方便的解决方案来处理不变性。这就是Immutable.js
所提供的,而打字稿永远不会提供,并且与您是否喜欢api无关。
答案 3 :(得分:1)
与readonly
相比,不变的js区别特征是structural sharing。
这是一般利益: 想象一下嵌套的JS对象,该对象在多层嵌套中具有16个属性。
使用readonly
来更新值的方法是复制旧值,修改所需的任何数据,然后获得新值!
使用JS
更新值的方法是保留所有未更改的属性,而仅复制那些已更改的属性(以及它们的父级,直到我们到达根目录为止)。
因此,不可变js节省了更新时间(减少了复制),节省了内存(减少了复制),并在决定是否需要重做一些相关工作时节省了时间(例如,我们知道有些叶子没有更改,因此它们的DOM不变)必须通过React进行更改!)。
您可以看到readonly
与Immutable js不在同一个联盟中。一个是突变属性,另一个是高效的不可变数据结构库。
答案 4 :(得分:0)
Typescript的边缘仍然很粗糙,并且具有不变性-并且它们(自Typescript 3.7起)仍未解决以下问题:可以通过先将readonly
对象分配给非readonly
来对其进行突变对象。
但是可用性仍然相当不错,因为它涵盖了几乎所有其他用例。
我在this comment中找到的这个定义对我来说很有效:
type ImmutablePrimitive = undefined | null | boolean | string | number | Function;
export type Immutable<T> =
T extends ImmutablePrimitive ? T :
T extends Array<infer U> ? ImmutableArray<U> :
T extends Map<infer K, infer V> ? ImmutableMap<K, V> :
T extends Set<infer M> ? ImmutableSet<M> : ImmutableObject<T>;
export type ImmutableArray<T> = ReadonlyArray<Immutable<T>>;
export type ImmutableMap<K, V> = ReadonlyMap<Immutable<K>, Immutable<V>>;
export type ImmutableSet<T> = ReadonlySet<Immutable<T>>;
export type ImmutableObject<T> = { readonly [K in keyof T]: Immutable<T[K]> };