天天看点

Immutable vs. seamless-immutable?可变性方案之争!

作者:前端进阶

大家好,很高兴又见面了,我是"前端‬进阶‬",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!

Immutable vs. seamless-immutable?可变性方案之争!

前端‬进阶‬

今天给大家带来的是Immutable vs. seamless-immutable的可变性方案之争,话不多少,直接开始。

1.什么是不变性?

Immutable vs. seamless-immutable?可变性方案之争!

图片来自Priya Pedamkar的“Mutable vs Immutable Java”

不变性是 Clojure 或 Lisp 等函数式语言中最著名的概念,本质是无论如何都不能改变数据。 但这是否意味着一旦创建了数据就无法更改它? 不,其实不然!只是意味着,每次想要更改数据中的任何内容时,都必须创建完整的副本,然后将更改应用到副本中再返回,即将所有数据结构视为以只读模式工作,而不能直接在上面修改,从而避免一些可能的问题。 比如下面的例子:

const arr = [1, 2, 3];
//想将数组中的第二个元素更改为 4,
//但也希望数据不可变,该怎么办?
const changedArr = [...arr.slice(0, 1), 4, ...arr.slice(2)];
// 下面是对象
const obj = {
  foo: 'bar',
};
const changedObj = {
  ...foo,
  // 创建副本
  bar: 'baz',
};           

2.为什么不可变性如此重要?

将代码中的每个数据结构都设置为不可变,可以让代码完全按照开发者想要的方式运行。 在 JavaScript 世界中,主要使用对值的引用,而不是值本身。 这意味着,每个有权访问引用的代码都可以更改它引用的值:

const foo = {
  bar: 'baz',
  // bar属性为baz字符串
};           
someMysteriousFunction(foo); 
// 真的相信这个函数,它不会改变foo对象吗?
console.log(foo.bar); 
// 能确定它会打印“baz”吗?不能!           

在上面的示例中, someMysteriousFunction 可能会改变 foo 属性。 这意味着开发者不能相信这个函数,不能相信任何可以访问该对象引用的代码, 除非它是不可变的。

Immutable vs. seamless-immutable?可变性方案之争!

不变性在 Redux 的上下文中尤为重要

不变性在 Redux 的上下文中尤为重要,因为它使 Redux 能够正确且快速地工作。 Redux(和 react-redux)使用浅层相等比较来决定状态和组件的更新,因此Redux 要求 reducer 是没有副作用的纯函数。

这意味着你必须从 reducer 返回一个新状态,而不是改变当前状态。 这个约定使 Redux 变得快速、可靠,它甚至可以启用时间旅行调试等高级功能。

3.Immutable.js现有问题?

上文已经知道不可变性是一个有价值的概念,而 Immutable.js通过引入自己的List, Stack, Map, OrderedMap, Set, OrderedSet 和Record等自定义对象,同时内置了对象的方法,也确实做到了这一点。

const { Map } = require('immutable');
// Map
const map1 = Map({ a: 1, b: 2, c: 3 });
const map2 = map1.set('b', 50);
map1.get('b') + ' vs. ' + map2.get('b'); 
// 2 vs. 50           

然而,Immutable.js因为本身的设计问题,依然有一些和开发者无法调和的矛盾。

  • 大量样板文件:在处理对象、数组时不能使用标准语法,而是必须使用 API
  • 大多数库与它不兼容:必须广泛使用 fromJS 和 toJS 工具方法
  • 修改成本高:因为它的语法存在于整个代码库中
  • 性能提升不明显:在绝大多数使用它的应用程序中很少真正有预期的性能提升
  • 明显拖慢开发速度
Immutable vs. seamless-immutable?可变性方案之争!

图片来自Aleksandar Ginovski的《4 Reasons To Love ImmutableJS》

4.Immutable.js的可行替代方案

既然Immutable.js有这么多问题,那么开发者应该如何解决呢?即,如何在项目中能有效的使用不可变的特性。简单场场景可以使用Rest替代,在复杂场景强烈建议使用seamless-immutable。

4.1 对象Rest运算符

Rest符的工作方式类似于 Object.assign ,但更加简洁。 它在所有现代 JavaScript 运行时中都是开箱即用的,也可以使用 Babel 等工具将其转换为旧的 ES5 代码。

const reducer = (state, action) => ({
  ...state,
  myChangingProp: action.payload,
});           

它是一种非常简单和优雅的语法,而且不需要使用任何import,开箱即用!当有更复杂的对象树并且想要更改嵌套很深的属性时,它的问题就出现了:

const reducer = (state, action) => ({
  ...state,
  nestedObj: {
    // 嵌套复制的麻烦
    ...state.nestedObj,
    anotherNestedObj: {
      ...state.nestedObj.anotherNestedObj,
      finallyAPropToChange: action.payload,
    },
  },
});           

4.2 seamless-immutable

seamless-immutable是向后兼容普通数组和对象的不可变 JS 数据结构。 可以在 for 循环中使用它们,或将它们传递给需要普通 JavaScript 数据结构的函数等。

var array = Immutable(["totally", "immutable", {hammer: "Can’t Touch This"}]);
array[1] = "I'm going to mutate you!"
array[1] // "immutable"
array[2].hammer = "hm, surely I can mutate this nested object..."
array[2].hammer // "Can’t Touch This"
for (var index in array) { console.log(array[index]); }
// "totally"
// "immutable"
// { hammer: 'Can’t Touch This' }
JSON.stringify(array) 
//'["totally","immutable",{"hammer":"Can’t Touch This"}]'           

注意:seamless-immutable的向后兼容性要求 Object.defineProperty 和 Object.freeze 等 ECMAScript 5 功能存在并正常工作。看一个使用seamless-immutable操作数据的示例:

import Immutable from 'seamless-immutable';
const initialState = Immutable({});
const reducer = (state, action) =>
  state.setIn(['nestedObj', 'anotherNestedObj', 'finallyAPropToChange'], action.payload);           

在seamless-immutable中,状态state不再是普通的 JavaSript 对象,而是通过Immutable包装后的对象,同时还必须使用 API 来进行更改操作。

虽然必须使用 API 来“改变”数据,但可以使用标准的点或括号表示法来访问它,也是因为seamless-immutable向后兼容普通 JS 对象和数组。

5.Immutable.js vs. seamless-immutable

包体积大小&加载速度

从包体积来看,immutable压缩后体积达到了17.7k,而seamless-immutable只有2.7k,前者是后者的6.5倍。同时,在包的下载速度上,3G网络下immutable需要353ms,4G下也需要20ms,远大于seamless-immutable在3G下的54ms和4G网络下的3ms。

Immutable vs. seamless-immutable?可变性方案之争!

Github/NPM的数据

从Github的数据来看,immutable创建于10年前,seamless-immutable创建于8年前,前者的star数达到了32,454,而后者只有5,368。所以,从整体看来,seamless-immutable相对比较年轻,在知名度上也会稍微差一点。

Immutable vs. seamless-immutable?可变性方案之争!

同时,放开来看过去5年的数据,immutable处于急剧的爆发期,而seamless-immutable则相对平缓很多。

Immutable vs. seamless-immutable?可变性方案之争!

从周下载量来看immutable也远胜于seamless-immutable。

Immutable vs. seamless-immutable?可变性方案之争!

seamless-immutable vs. immutable周下载量

综合来看,如果对于项目有严格的性能要求,非常建议大家使用seamless-immutable而不是immutable。虽然seamless-immutable知名度稍低于immutable,但是不失为一个优秀的可变性解决方案。

6.本文总结

本文主要和大家介绍Immutable.js的可行替代方案seamless-immutable,文章从Github数据、NPM周下载量、使用趋势、性能等诸多维度进行了对比,相信大家对两者已经有一个比较深入的理解。文末的参考资料提供了大量优秀文档以供学习,如果有兴趣可以自行阅读。如果大家有什么疑问欢迎在评论区留言。

参考资料

https://leocode.com/development/you-may-not-need-immutable-js/

https://www.educba.com/mutable-vs-immutable-java/

https://www.npmjs.com/package/seamless-immutable

https://www.npmjs.com/package/immutable