在github上编辑这篇文章

immutability 助手#

React 让你可以使用任何你想要的数据管理方式,包括 mutation。然而,如果你可以在你的应用的性能关键性部分里使用 immutable 数据,将会易于实现一个快速的 shouldComponentUpdate() 方法来显著加速你的 app。

在 JavaScript 里处理 immutable 数据比在为此设计的语言中要难的多,比如 Clojure。然而,我们提供了一个简单的 immutability 助手,update(),它使处理这类数据容易多了,不用 根本性的改变你的数据的表达方式。你也同样可以看一看 Facebook 的 Immutable-jsAdvanced Performance 了解更多关于 Immutable-js 的信息。

主要的思路#

如果你像这样变动数据:

myData.x.y.z = 7;
// or...
myData.a.b.push(9);

你将没有任何办法决定哪个数据被改变了,因为之前的拷贝已经被覆盖。作为替代,你需要创建一个新的 myData 的拷贝并且只修改需要改变的地方。然后你可以用三个等于在 shouldComponentUpdate() 里比较旧的 myData 拷贝与新的拷贝:

var newData = deepCopy(myData);
newData.x.y.z = 7;
newData.a.b.push(9);

不幸的是,深拷贝很昂贵,并且有时候不可能。你可以通过仅仅拷贝需要被改变和重用没有改变的对象来缓解这个情况。不幸的是,在当今的 JavaScript 里这会很笨重:

var newData = extend(myData, {
  x: extend(myData.x, {
    y: extend(myData.x.y, {z: 7}),
  }),
  a: extend(myData.a, {b: myData.a.b.concat(9)})
});

虽然这相当高性能(因为只对 log n 的对象进行了浅拷贝并重用了剩下的),但它写起来很痛苦。看看所有重复的代码!这不仅仅是烦人的,同时也提供了一大片 bugs 区域。

update() 提供了这个模式的简单语法糖来使写这类代码更容易。上面的代码变成:

var update = require('react-addons-update');

var newData = update(myData, {
  x: {y: {z: {$set: 7}}},
  a: {b: {$push: [9]}}
});

虽然这个语法需要花一些时间来适应(它的灵感来自于 MongoDB's query language),但是没有冗余,它可静态分析并且没有 mutative 版本那么多键入。

$-前缀的 keys 被称为 命令。被 "变动的" 数据结构被称为 目标

有效的命令#

  • {$push: array} 在目标上 push() 所有 array 里的项目。
  • {$unshift: array} 在目标上 unshift() 所有 array 里的项目。
  • {$splice: array of arrays} 在目标上对于每一个 arrays 里的项目使用项目提供的参数调用 splice()
  • {$set: any} 整个替换目标.
  • {$merge: object} 合并 目标和 object 的 keys.
  • {$apply: function} 传递当前的值给 function 并用返回值更新它。

例子#

简单的 push#

var initialArray = [1, 2, 3];
var newArray = update(initialArray, {$push: [4]}); // => [1, 2, 3, 4]

initialArray is still [1, 2, 3].

嵌套的 collections#

var collection = [1, 2, {a: [12, 17, 15]}];
var newCollection = update(collection, {2: {a: {$splice: [[1, 1, 13, 14]]}}});
// => [1, 2, {a: [12, 13, 14, 15]}]

本例访问了 collection2索引下的键a,并且拼接了一个从索引1开始(移除17)并插入1314的项目。

基于当前的值更新新值#

var obj = {a: 5, b: 3};
var newObj = update(obj, {b: {$apply: function(x) {return x * 2;}}});
// => {a: 5, b: 6}
// This is equivalent, but gets verbose for deeply nested collections:
var newObj2 = update(obj, {b: {$set: obj.b * 2}});

(浅) 合并#

var obj = {a: 5, b: 3};
var newObj = update(obj, {$merge: {b: 6, c: 7}}); // => {a: 5, b: 6, c: 7}