React是web前端UI库,关注的是如何高效地根据数据渲染UI界面。virtual dom 与高效的diff算法结合,使得当数据发生变更时,react 能通过简洁高效的算法找出dom中真正变更的部分,同时根据变更批量进行dom操作渲染UI界面。
传统的根据一个树形结构到另外一个树形结构的装换操作是一个复杂且耗时的过程,一般时间复杂度会达到O(n^3),加上每个dom节点都是一个附带大量信息的对象,一般的算法很难做到实时根据数据变更,更新UI视图。
React根据dom树的特定场景,制定了大胆且高效的diff策略:
1. Web UI中dom节点的跨层次的移动操作很少,可忽略
2. 拥有相同类型的组件有相似的树形结构,不同类型的节点的树形结构大概率不相同
3. 对于同一层次的节点,可以通过唯一的id进行区分
根据上面的3点策略,两颗dom数的比较,只会对同一层次的节点进行比对。
<1>当出现跨层次移动时,react只会做创建和删除操作,这是一种影响算法性能的操作,因此React 建议不要进行dom跨层次的操作。开发组件时,保持稳定的dom结构会有助于性能的提升,可以通过CSS隐藏或显示节点,而不是真的移除或添加节点。
<2>如果是同一类型的组件,则按照继续比较下一层的节点,如果不是同一类型的组件,则判断为dirty Component,从而根据新组件替换整个组件下的所有子组件。对于同一类型的组件,仅比对及更新有改变的属性。可能数据没有发生变更或者UI视图不需要根据,React 允许开发者通过shouldComponentUpdate(nextProps,nextState)来判断该组件是否需要进行diff。
<3>对于同一层次下的子组件,react 建议添加key来唯一的区分,以提高diff的效率。首先会对的新的节点进行遍历操作,通过唯一的key可以找出需要新建、删除、以及移位的节点。如果老集合中存在相同的节点则进行移位操作,避免的节点的新建删除开销(包括计算及dom操作)。移位操作算法如下:
let lastIndex = 0 // 记录当前元素之前的元素在oldList中最靠后的位置
for( let element in newList){`
// element._mountIndex 是当前元素在老List的位置,newIndex 是当前元素在新
// LIst中的位置
if(element._mountIndex < lastIndex)
move(element, newIndex)
lastIndex = Math.max(element._mountIndex, lastIndex)
}
策略是如果当前元素在老List中的位置,小于在它之前的元素在老List的中的最大位置,就说明当前元素在oldlist需要向后移动才能变成新的List。
总结:diff算法并不是要构造一个新的树,而比对找出变化的部分,来对老的dom树进行最少的操作,从而优化UI更新的效率。