TypeScript的`readonly`可以完全替代Immutable.js吗?

时间:2019-04-29 14:47:32

标签: reactjs typescript redux immutable.js

我已经使用React.js进行了几个项目。他们中的一些人使用了Flux,一些人使用Redux,还有一些只是使用Context的普通React应用。

我真的很喜欢Redux使用功能模式的方式。但是,开发人员很可能无意间改变了状态。在寻找解决方案时,基本上只有一个答案-Immutable.js。老实说,我讨厌这个图书馆。它完全改变了您使用JavaScript的方式。而且,它必须在整个应用程序中实现,否则当某些对象是纯JS而某些对象是不可变结构时,您最终会遇到奇怪的错误。或者您开始​​使用.toJS(),这-再次-非常非常糟糕。

最近,我的一位同事建议使用TypeScript。除了类型安全之外,它还具有一个有趣的功能-您可以定义自己的数据结构,其所有字段都标记为readonly。这样的结构本质上是不可变的。

我不是Immutable.js或TypeScript的专家。但是,在Redux存储区中拥有不可变数据结构且不使用Immutable.js的承诺似乎太过真实了。 TypeScript的readonly是否可以代替Immutable.js?还是有任何隐藏的问题?

5 个答案:

答案 0 :(得分:3)

虽然确实是TypeScript的readonly修饰符仅存在于设计类型,并且不影响运行时代码,但对于整个类型系统都是如此。也就是说,在运行时没有什么能阻止您将数字分配给类型为string的变量。因此,这个答案有点像是一条红鲱鱼……如果您在设计时被警告要尝试更改标记为constreadonly的内容,则可能无需进行广泛的设计运行时检查。

但是,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的所有用法都需要更改为ReadonlyArrayMap的所有用法都需要更改为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]> };