React根据JS对象构造的元素生成虚拟Dom树,对比虚拟Dom节点的变化来渲染真正的Dom树
因为传统Dom树的比较很慢,而Javascript中的虚拟dom比较很快,采用虚拟dom大大提高了性能
传统Dom Diff算法
传统 diff 算法通过循环递归对节点进行依次对比,效率低下,如果某个Dom节点被改变,将造成其所有子节点都被重新渲染。
传统Dom Diff算法算法复杂度为O(n^3),其中 n 是树中节点的总数。
React virtual dom diff
将 O(n^3) 复杂度的问题转换成 O(n) 复杂度
diff 策略
- 拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构。
- 对于同一层级的一组子节点,它们可以通过唯一 id 进行区分。
- React 分别对 tree diff、component diff 以及 element diff 进行算法优化
1.tree diff
React 对树进行分层比较,两棵树只会对同一层次的节点进行比较。
React 通过 updateDepth 对 Virtual DOM 树进行层级控制,只会对相同颜色方框内的 DOM 节点进行比较,即同一个父节点下的所有子节点。当发现节点已经不存在,则该节点及其子节点会被完全删除掉,不会用于进一步的比较。这样只需要对树进行一次遍历,便能完成整个 DOM 树的比较。
React 官方建议不要进行 DOM 节点跨层级的操作
在开发组件时,保持稳定的 DOM 结构会有助于性能的提升。例如,可以通过 CSS 隐藏或显示节点,而不是真的移除或添加 DOM 节点。
2.component diff
如果是同一类型的组件,按照原策略继续比较 virtual DOM tree。
如果不是,则将该组件判断为 dirty component,从而替换整个组件下的所有子节点。
对于同一类型的组件,有可能其 Virtual DOM 没有任何变化,如果能够确切的知道这点那可以节省大量的 diff 运算时间,因此 React 允许用户通过 shouldComponentUpdate() 来判断该组件是否需要进行 diff。
如图,当 component D 改变为 component G 时,即使这两个 component 结构相似,一旦 React 判断 D 和 G 是不同类型的组件,就不会比较二者的结构,而是直接删除 component D,重新创建 component G 以及其子节点。
3.element diff
当节点处于同一层级时,React diff 提供了三种节点操作:
- INSERT_MARKUP(插入),新的 component 类型不在老集合里, 即是全新的节点,需要对新节点执行插入操作。
- MOVE_EXISTING(移动),在老集合有新 component 类型,且 element 是可更新的类型,generateComponentChildren 已调用 receiveComponent,这种情况下 prevChild=nextChild,就需要做移动操作,可以复用以前的 DOM 节点。
- REMOVE_NODE(删除),老 component 类型,在新集合里也有,但对应的 element 不同则不能直接复用和更新,需要执行删除操作,或者老 component 不在新集合里的,也需要执行删除操作。
建议对同一层级的同组子节点,添加唯一 key 进行区分,这样在相同的节点但位置发生变化时,只要对这些节点进行位置移动即可,避免了低效的删除、创建操作。