React 让你可以使用任何你想要的数据管理方式,包括 mutation。然而,如果你可以在你的应用的性能关键性部分里使用 immutable 数据,将会易于实现一个快速的 shouldComponentUpdate()
方法来显著加速你的 app。
在 JavaScript 里处理 immutable 数据比在为此设计的语言中要难的多,比如 Clojure。然而,我们提供了一个简单的 immutability 助手,update()
,它使处理这类数据容易多了,不用 根本性的改变你的数据的表达方式。你也同样可以看一看 Facebook 的 Immutable-js 和Advanced 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 并用返回值更新它。var initialArray = [1, 2, 3];
var newArray = update(initialArray, {$push: [4]}); // => [1, 2, 3, 4]
initialArray
is still [1, 2, 3]
.
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]}]
本例访问了 collection
的2
索引下的键a
,并且拼接了一个从索引1
开始(移除17
)并插入13
和14
的项目。
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}