React 的 Reconciliation 算法原理
React 的渲染机制 Reconciliation 过程
React 采用的是虚拟 DOM (即 VDOM ),每次属性 (props) 和状态 (state) 发生变化的时候,render 函数返回不同的元素树,React 会检测当前返回的元素树和上次渲染的元素树之前的差异,然后针对差异的地方进行更新操作,最后渲染为真实 DOM,这就是整个 Reconciliation 过程,其核心就是进行新旧 DOM 树对比的 diff 算法。
diff 算法
在某一时间节点调用 React 的
render()
方法,会创建一棵由 React 元素组成的树。在下一次 state 或 props 更新时,相同的render()
方法会返回一棵不同的树。React 需要基于这两棵树之间的差别来判断如何有效率的更新 UI 以保证当前 UI 与最新的树保持同步。在上图第三部分,新旧DOM树比对所用的算法即 Diff算法
React diff策略
- Web UI 中 DOM 节点跨层级的移动操作特别少,可以忽略不计。【永远只比较同层节点,不会跨层级比较节点。】
- 拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构。
- 对于同一层级的一组子节点,它们可以通过唯一 key 进行区分。
基于以上三个前提策略,React 分别对 tree diff、component diff 以及 element diff 进行算法优化,事实也证明这三个前提策略是合理且准确的,它保证了整体界面构建的性能。
执行流程(规则)
1、元素类型不相同时
直接将 原 VDOM 树上该节点以及该节点下所有的后代节点
全部删除,然后替换为新 VDOM 树上同一位置的节点
当根节点为不同类型的元素时,React 会拆卸原有的树并且建立起新的树。当拆卸一棵树时,对应的 DOM 节点也会被销毁。组件实例将执行 componentWillUnmount() 方法。当建立一棵新的树时,对应的 DOM 节点会被创建以及插入到 DOM 中。组件实例将执行 componentWillMount() 方法,紧接着 componentDidMount() 方法。所有跟之前的树所关联的 state 也会被销毁。
2. 元素类型相同时
a. 都是 DOM 节点
React 会保留 DOM 节点,仅比对及更新有改变的属性。
<div className="old" title="老节点" /><div className="new" title="新节点" />
通过比对这两个元素,React 知道需要修改 DOM 元素上的 className 属性和 title 属性。
处理完该节点后,React 继续对子节点进行递归。
b. 都是组件元素
对于同一类型的组件,根据Virtual DOM是否变化也分两种,可以用shouldComponentUpdate()判断Virtual DOM是否发生了变化,若没有变化就不需要再进行diff,这样可以节省大量时间,若变化了,就按原策略进行比较
对于非同一类的组件,则将该组件判断为 dirty component,从而替换整个组件下的所有子节点。
当一个组件更新时,组件实例保持不变,这样 state 在跨越不同的渲染时保持一致。React 将更新该组件实例的 props 以跟最新的元素保持一致,并且调用该实例的 componentWillReceiveProps() 和 componentWillUpdate() 方法。