不可变数据的辅助工具(Immutability Helpers)
最后更新于:2022-04-01 20:48:13
React让你可以使用任何你想要的数据管理风格,包括数据可变风格。然而,如果你能够在你应用中讲究性能的部分使用不可变数据,就可以很方便地实现一个快速的`shouldComponentUpdate()`方法来显著提升你应用的速度。
在JavaScript中处理不可变数据比在语言层面上就设计好要难,像[Clojure](http://clojure.org/)。但是,我们提供了一个简单的不可变辅助工具,`update()`,这就让处理这种类型的数据更加简单了,根本_不会_改变你数据的表示的形式。(Dealing with immutable data in JavaScript is more difficult than in languages designed for it, like [Clojure](http://clojure.org/). However, we've provided a simple immutability helper, `update()`, that makes dealing with this type of data much easier, _without_fundamentally changing how your data is represented.)
## 主要思想(The main idea)
如果你像这样改变数据:
~~~
myData.x.y.z = 7;
// or...
myData.a.b.push(9);
~~~
你无法确定哪个数据改变了,因为之前的副本被覆盖了。相反,你需要创建一个新的`myDate`副本,仅仅改变需要改变的部分。然后你就能够在`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`个对象,重用余下的),但是写起来很痛苦。看看所有的重复书写!这不仅仅是恼人,也提供了一个巨大的出bug的区域。
`update()`在这种情形下提供了简单的语法糖,使得写这种代码变得更加简单。代码变为:
~~~
var newData = React.addons.update(myData, {
x: {y: {z: {$set: 7}}},
a: {b: {$push: [9]}}
});
~~~
虽然这种语法糖需要花点精力适应(尽管这是受[MongoDB's query language](http://docs.mongodb.org/manual/core/crud-introduction/#query)的启发),但是它没有冗余,是静态可分析的,并且比可变的版本少打了很多字。(While the syntax takes a little getting used to (though it's inspired by [MongoDB's query language](http://docs.mongodb.org/manual/core/crud-introduction/#query)) there's no redundancy, it's statically analyzable and it's not much more typing than the mutative version.)
以`$`为前缀的键被称作_命令_。他们“改变”的数据结构被称为_目标_。( The `$`-prefixed keys are called _commands_. The data structure they are "mutating" is called the _target_.)
## 可用的命令(Available commands)
* `{$push: array}` 利用`push()`把目标上所有的元素放进`数组`(`push()` all the items in `array` on the target.)。
* `{$unshift: array}` 利用`unshift()`把目标上所有的元素放进`数组`(`unshift()` all the items in `array` on the target.)。
* `{$splice: array of arrays}` 对于`array`中的每一个元素,用元素提供的参数在目标上调用`splice()`(for each item in `arrays` call `splice()` on the target with the parameters provided by the item.)。
* `{$set: any}` 整体替换目标(replace the target entirely.)。
* `{$merge: object}` 合并目标和`object`的键。
* `{$apply: function}` 传入当前的值到函数,然后用新返回的值更新它(passes in the current value to the function and updates it with the new returned value.)。
## 示例
### 简单的入栈
~~~
var initialArray = [1, 2, 3];
var newArray = update(initialArray, {$push: [4]}); // => [1, 2, 3, 4]
~~~
`initialArray`仍然是`[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`。(This accesses `collection`'s index `2`, key `a`, and does a splice of one item starting from index `1` (to remove `17`) while inserting `13` and`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}
~~~
';