在《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】
参考资料: