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}
~~~