1、什么是diff算法传统diff:diff算法即差异查找算法;对于html dom结构即为tree的差异查找算法;而对于计算两颗树的差异时间复杂度为o(n^3),显然成本太高,react不可能采用这种传统算法;
react diff:
之前说过,react采用虚拟dom技术实现对真实dom的映射,即react diff算法的差异查找实质是对两个javascript对象的差异查找;
基于三个策略:
web ui 中 dom 节点跨层级的移动操作特别少,可以忽略不计。(tree diff)
拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结(component diff)
对于同一层级的一组子节点,它们可以通过唯一 id 进行区分。(element diff)
2、react diff算法解读首先需要明确,只有在react更新阶段才会有diff算法的运用;
react更新机制:
react diff算法优化策略图:
react更新阶段会对reactelement类型判断而进行不同的操作;reactelement类型包含三种即:文本、dom、组件;
每个类型的元素更新处理方式:
自定义元素的更新,主要是更新render出的节点,做甩手掌柜交给render出的节点的对应component去管理更新。
text节点的更新很简单,直接更新文案。
浏览器基本元素的更新,分为两块:
更新属性,对比出前后属性的不同,局部更新。并且处理特殊属性,比如事件绑定。
子节点的更新,子节点更新主要是找出差异对象,找差异对象的时候也会使用上面的shouldupdatereactcomponent来判断,如果是可以直接更新的就会递归调用子节点的更新,这样也会递归查找差异对象。不可直接更新的删除之前的对象或添加新的对象。之后根据差异对象操作dom元素(位置变动,删除,添加等)。
事实上diff算法只被调用于react更新阶段的dom元素更新过程;为什么这么说?
1、 如果为更新文本类型,内容不同就直接更新替换,并不会调用复杂的diff算法:
reactdomtextcomponent.prototype.receivecomponent(nexttext, transaction) { //与之前保存的字符串比较 if (nexttext !== this._currentelement) { this._currentelement = nexttext; var nextstringtext = '' + nexttext; if (nextstringtext !== this._stringtext) { this._stringtext = nextstringtext; var commentnodes = this.gethostnode(); // 替换文本元素 domchildrenoperations.replacedelimitedtext( commentnodes[0], commentnodes[1], nextstringtext ); } } }
2、对于自定义组件元素:
class tab extends component { constructor(props) { super(props); this.state = { index: 1, } } shouldcomponentupdate() { .... } render() { return ( <p> <p>item1</p> <p>item1</p> </p> ) } }
需要明确的是,何为组件,可以说组件只不过是一段html结构的包装容器,并且具备管理这段html结构的状态等能力;
如上述tab组件:它的实质内容就是render函数返回的html结构,而我们所说的tab类就是这段html结构的包装容器(可以理解为一个包装盒子);
在react渲染机制图中可以看到,自定义组件的最后结合react diff优化策略一(不同类的两个组件具备不同的结构)
3、基本元素:
reactdomcomponent.prototype.receivecomponent = function(nextelement, transaction, context) { var prevelement = this._currentelement; this._currentelement = nextelement; this.updatecomponent(transaction, prevelement, nextelement, context);}reactdomcomponent.prototype.updatecomponent = function(transaction, prevelement, nextelement, context) { //需要单独的更新属性 this._updatedomproperties(lastprops, nextprops, transaction, iscustomcomponenttag); //再更新子节点 this._updatedomchildren( lastprops, nextprops, transaction, context ); // ......}
在this._updatedomchildren方法内部才调用了diff算法。
3、react中diff算法的实现_updatechildren: function(nextnestedchildrenelements, transaction, context) { var prevchildren = this._renderedchildren; var removednodes = {}; var mountimages = []; // 获取新的子元素数组 var nextchildren = this._reconcilerupdatechildren( prevchildren, nextnestedchildrenelements, mountimages, removednodes, transaction, context ); if (!nextchildren && !prevchildren) { return; } var updates = null; var name; var nextindex = 0; var lastindex = 0; var nextmountindex = 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) { // 同一个引用,说明是使用的同一个component,所以我们需要做移动的操作 // 移动已有的子节点 // notice:这里根据nextindex, lastindex决定是否移动 updates = enqueue( updates, this.movechild(prevchild, lastplacednode, nextindex, lastindex) ); // 更新lastindex lastindex = math.max(prevchild._mountindex, lastindex); // 更新component的.mountindex属性 prevchild._mountindex = nextindex; } else { if (prevchild) { // 更新lastindex lastindex = math.max(prevchild._mountindex, lastindex); } // 添加新的子节点在指定的位置上 updates = enqueue( updates, this._mountchildatindex( nextchild, mountimages[nextmountindex], lastplacednode, nextindex, transaction, context ) ); nextmountindex++; } // 更新nextindex nextindex++; lastplacednode = reactreconciler.gethostnode(nextchild); } // 移除掉不存在的旧子节点,和旧子节点和新子节点不同的旧子节点 for (name in removednodes) { if (removednodes.hasownproperty(name)) { updates = enqueue( updates, this._unmountchild(prevchildren[name], removednodes[name]) ); } } }
4、基于中diff的开发建议基于tree diff:
开发组件时,注意保持dom结构的稳定;即,尽可能少地动态操作dom结构,尤其是移动操作。
当节点数过大或者页面更新次数过多时,页面卡顿的现象会比较明显。
这时可以通过 css 隐藏或显示节点,而不是真的移除或添加 dom 节点。
基于component diff:
注意使用 shouldcomponentupdate() 来减少组件不必要的更新。
对于类似的结构应该尽量封装成组件,既减少代码量,又能减少component diff的性能消耗。
基于element diff:
对于列表结构,尽量减少类似将最后一个节点移动到列表首部的操作,当节点数量过大或更新操作过于频繁时,在一定程度上会影响 react 的渲染性能。
以上就是react中diff算法是什么?diff算法的策略及实现的详细内容。
