在《React源碼分析 - 組件更新與事務》中的流程圖的最後:javascript
藍色框框的部分分別是Diff算法的核心代碼updateChildren以及processUpdates,經過Diff算法獲取了組件更新的updates隊列以後一次性進行更新。java
Diff算法的代碼(先彆着急下面會具體解釋算法的主要步驟):node
_updateChildren: function (nextNestedChildrenElements, transaction, context) {
var prevChildren = this._renderedChildren;
var removedNodes = {};
var nextChildren = this._reconcilerUpdateChildren(prevChildren, nextNestedChildrenElements, removedNodes, transaction, context);
if (!nextChildren && !prevChildren) {
return;
}
var updates = null;
var name;
var lastIndex = 0;
var nextIndex = 0;
var lastPlacedNode = null;
for (name in nextChildren) {
if (!nextChildren.hasOwnProperty(name)) {
continue;
}
var prevChild = prevChildren && prevChildren[name];
var nextChild = nextChildren[name];
if (prevChild === nextChild) {
updates = enqueue(updates, this.moveChild(prevChild, lastPlacedNode, nextIndex, lastIndex));
lastIndex = Math.max(prevChild._mountIndex, lastIndex);
prevChild._mountIndex = nextIndex;
} else {
if (prevChild) {
lastIndex = Math.max(prevChild._mountIndex, lastIndex);
}
updates = enqueue(updates, this._mountChildAtIndex(nextChild, lastPlacedNode, nextIndex, transaction, context));
}
nextIndex++;
lastPlacedNode = ReactReconciler.getNativeNode(nextChild);
}
for (name in removedNodes) {
if (removedNodes.hasOwnProperty(name)) {
updates = enqueue(updates, this._unmountChild(prevChildren[name], removedNodes[name]));
}
}
if (updates) {
processQueue(this, updates);
}
this._renderedChildren = nextChildren;
}
複製代碼
《深刻React技術棧》這本書對Diff算法的解釋比較好。其實只要記住幾個原則以及在具體的計算updates隊列的時候的算法優化的點就行了。react
傳統的diff算法的複雜度是O(n^3),想要具體的瞭解能夠去看"A Survey on Tree Edit Distance and Related Problems"算法
這種複雜度在實際中應用會爆炸的,雖然如今的電腦的CPU很強,但一個頁面也不能這樣任性~。數組
對此React的作法是給出合理的假設和方法來讓整個diff過程合理簡化。瀏覽器
基於上面的幾條,在具體的Diff過程當中React只進行分層比較,新舊的樹之間只比較同一個層次的節點。節點的操做分爲3種:插入、移動和刪除。dom
節點移動操做判斷的過程,引用《深刻React技術棧》中的話:ide
首先對新集合的節點進行循環遍歷,for (name in nextChildren),經過惟一 key 能夠判斷新老集合中是否存在相同的節點,if (prevChild === nextChild),若是存在相同節點,則進行移動操做,但在移動前須要將當前節點在老集合中的位置與 lastIndex 進行比較,if (child._mountIndex < lastIndex),則進行節點移動操做,不然不執行該操做。這是一種順序優化手段,lastIndex 一直在更新,表示訪問過的節點在老集合中最右的位置(即最大的位置),若是新集合中當前訪問的節點比 lastIndex 大,說明當前訪問節點在老集合中就比上一個節點位置靠後,則該節點不會影響其餘節點的位置,所以不用添加到差別隊列中,即不執行移動操做,只有當訪問的節點比 lastIndex 小時,才須要進行移動操做。源碼分析
須要注意的是」這是一種順序優化手段,lastIndex 一直在更新,表示訪問過的節點在老集合中最右的位置(即最大的位置),若是新集合中當前訪問的節點比 lastIndex 大,說明當前訪問節點在老集合中就比上一個節點位置靠後,則該節點不會影響其餘節點的位置,所以不用添加到差別隊列中,即不執行移動操做「這句話。意思是若是一個節點在舊集合中的位置已經在你以前進行判斷的最後一個節點的背後,那麼這個節點已經在被diff過的節點的後面了和以前的diff過的節點在順序上就已是正確的了,不須要移動了,反之的節點須要被移動。
另外須要知道的是若是沒有給key賦值,React會默認使用的是遍歷過程當中的 index 值。這裏的index值指的是節點遍歷的順序號,效果等同於有些小夥伴用列表數組的index來當作key。這樣實際上是很差的,由於節點的key和節點的位置有關係和節點自己不要緊,也就是若是我一個列表有10個節點,按照遍歷的順序key爲1到10,而後我在列表的最開始增長了一個節點,這個時候按照列表遍歷的順序來設置key,則原來的10個節點的key都變了,並且新舊節點的key錯誤的對上了,要知道key在React中時對一個組件的身份識別的標示,錯誤或者重複的key會形成React錯誤的結果......so.......key須要是一個和節點自己有聯繫的惟一標示。
react的做者之一Paul O’Shannessy有提到:
Key is not really about performance, it’s more about identity (which in turn leads to better performance). Randomly assigned and changing values do not form an identity
你可能會問,上面的diff算法的源碼部分沒看到key啊,恩,其實每一個component的key會變成nextChildren&prevChildren對象中的name對應的value是component,另外在_reconcilerUpdateChildren中的shouldUpdateReactComponent組件的key也有使用到。
對於新增和刪除節點的操做簡單來講:
固然上面說的移動、新增和刪除節點的操做,不是立刻執行的,而是收集到updates數組中,而後用processUpdates方法一次性進行具體的DOM的的更新。
processUpdates: function (parentNode, updates) {
for (var k = 0; k < updates.length; k++) {
var update = updates[k];
switch (update.type) {
case ReactMultiChildUpdateTypes.INSERT_MARKUP:
insertLazyTreeChildAt(parentNode, update.content, getNodeAfter(parentNode, update.afterNode));
break;
case ReactMultiChildUpdateTypes.MOVE_EXISTING:
moveChild(parentNode, update.fromNode, getNodeAfter(parentNode, update.afterNode));
break;
case ReactMultiChildUpdateTypes.SET_MARKUP:
setInnerHTML(parentNode, update.content);
break;
case ReactMultiChildUpdateTypes.TEXT_CONTENT:
setTextContent(parentNode, update.content);
break;
case ReactMultiChildUpdateTypes.REMOVE_NODE:
removeChild(parentNode, update.fromNode);
break;
}
}
}
複製代碼
其中的節點的具體的操做就是到具體的瀏覽器的DOM的節點的操做了,舉個栗子。
function insertLazyTreeChildAt(parentNode, childTree, referenceNode) {
DOMLazyTree.insertTreeBefore(parentNode, childTree, referenceNode);
}
var insertTreeBefore = createMicrosoftUnsafeLocalFunction(function (parentNode, tree, referenceNode) {
if (tree.node.nodeType === 11) {
insertTreeChildren(tree);
parentNode.insertBefore(tree.node, referenceNode);
} else {
parentNode.insertBefore(tree.node, referenceNode);
insertTreeChildren(tree);
}
});
複製代碼
Node.insertBefore()就是瀏覽器DOM操做的API了。
想要跟着具體的Diff的過程來理解的話,推薦單步調試或者看《深刻React技術棧》中的栗子,這裏我就不畫了.....畫圖很累的.....網上也很是多相似的搜一下就行了。
本文對key的具體的使用的部分有待進一步深刻。【TBD】
參考資料: