React学习(9)—— 高阶应用:虚拟Dom差异比对算法
React提供了一系列声明性的API接口,因此在使用时不必担心每次库的更新会修改API接口。这样可以降低编写应用的复杂度,但是带来的问题是无法很好的理解React是如何实现这些功能的。这篇文章会介绍React的差异比对算法——“融合算法”是如何执行的。 差异匹配算法实现的前提我们先来看看第一个值得关注的我问题: 针对以上问题,有一些通用的算法可供参考,比如比对2颗树的差异,在前一个颗树的基础上生成最小操作树,但是这个算法的时间复杂度为n的三次方=O(n*n*n),当树的节点较多时,这个算法的时间代价会导致算法几乎无法工作。 架设在我们使用React时,一共部署了1000个元素,那么使用上面的算法,我们要比对数亿次才能得到比对的结果,根本不可能在一个浏览器中短时间完成。React实现了一个计算复杂度是O(n)的算法来解决这个问题,这个算法基于2个假设:
实际上,这个假设对于几乎所有的实际用例都是有效的。 差异算法对于2颗有差异的树,React首先比对2颗树的根节点。根据跟节点的类型是否相同,算法接下来会执行不同的操作。 Types不一样一旦2棵树之间的根元素类型不一样,React会直接移除旧的树并构建出新的树。例如从 重构一棵新的树时,所有的旧节点都会移除。组件的 根据这个特性,根节点之后的所有组件都会卸载并重建,状态也会随之改变。例如下面2个组件对比: <div> <Counter /> </div> <span> <Counter /> </span>
Dom元素拥有相同的类型当比较React元素为相同类型时,React会查看元素上的属性来比对。比对之后,React会保持的Dom节点不改变然后仅仅更新不同的属性值,例如: <div className="before" title="stuff" /> <div className="after" title="stuff" /> 在比对这2个元素之后,React知道仅仅需要修改当前Dom的 <div style={{color: 'red',fontWeight: 'bold'}} /> <div style={{color: 'green',fontWeight: 'bold'}} /> 在转换这2个组件时,React知道仅仅需要修改color的样式,而fontWeight不必发生变动。 在处理完当前Dom节点后,React依次对子节点进行递归。 组件元素拥有相同的类型当一个组件发生更新后,实例依然是原来的实例,所以状态还是以前的状态。React通过属性值(props)的更新来影响需要更新组件,此时组件实例的 然后, 递归子元素默认情况下,在递归子元素的Dom节点时,React同时对2个子元素列表进行迭代比对,如果发现差异都会产生一个突变(关于突变的概念请见React学习第六篇性能优化介绍不可变数据结构部分)。 例如,当增加一个元素在子元素的队尾,这2颗树的转换效率很高: <ul> <li>first</li> <li>second</li> </ul> <ul> <li>first</li> <li>second</li> <li>third</li> </ul> React先匹配 如果代码按下面的方式修改2颗树,执行的效率相对较差: <ul> <li>Duke</li> <li>Villanova</li> </ul> <ul> <li>Connecticut</li> <li>Duke</li> <li>Villanova</li> </ul> React会突变修改所有的子节点,最终 Keys为了解决上面的问题,React提供了一个“key”属性。当所有的子元素都有一个key值,React直接使用key值来比对树形结构中的所有子节点列表。例如为上面的的例子增加一个key会大大的提升转换效率: <ul> <li key="2015">Duke</li> <li key="2016">Villanova</li> </ul> <ul> <li key="2014">Connecticut</li> <li key="2015">Duke</li> <li key="2016">Villanova</li> </ul> 现在React可以知道key='2014'的节点是一个新值另外2个节点仅仅需要移动一下位置。 在实际使用中,key值并不难找。在常规业务中,很多列表都自然包含业务相关的ID了: <li key={item.id}>{item.name}</li> 当无法使用业务ID时,也可以额外增加一个ID值来标记列表差异,比如根据要使用的数据生成一个hash值,React不需要key值全局唯一,只需要在兄弟节点之间保持唯一即可。 最差情况下,你可以使用索引数据(0、1、2、....n)。使用索引需要注意的是,如果列表发生重新排序效率会很糟糕。 更多折中的考虑在使用React时需要谨记“一致性算法”是一个实现细则,他描述了React在各种情况下是如何工作的。在某些极端情况下,虽然最终呈现效果是一致的但是有可能每一个简单的操作都导致React全局重新渲染。本篇文章的目的就是启发普通用户在使用React开发时能获得更好的执行效率。 React在目前的实现中还存在一个问题,可以快捷的告知React子树中某个节点的位置已经发生改变,但是无法告知React他移动到了什么位置。因此在遇到这种情况时,算法会重构整个子树。 这是因为React依赖启发式算法,如果本文开篇提到的2个基本假设不成立,那么会导致算法效率极差。
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |