从 React 15 入手,理解 栈调和 算法
调和(Reconciliation),又译为 协调。
通过如 ReactDOM 等类库使之与 真实 DOM 同步。 这一过程叫做协调(调和)。
调和指的是将虚拟 DOM 映射到真实 DOM 的过程,Diff 过程只是其中一个环节。
React 源码结构佐证了这一点:React 从大的板块上将源码划分为 Core、Renderer、Reconciler 三部分。
其中 Reconciler 调和器所做的工作是一系列的,包括组件的挂载、卸载、更新等过程,其中更新过程涉及对 Diff 算法的调用。
所以,调和 !== Diff
。但 Diff 确实是调和过程中最具有代表性的一环。
根据 Diff 实现形式的不同,调和过程被划分为 以 React 15 为主的"栈调和" 以及 以 React 16 为主的"Fiber 调和"。
Diff 算法本质上是找不同的过程。
计算机领域上找出两个树结构之间的不同,需要通过循环递归进行树节点的一一对比。这个时间复杂度是 O(n^3)。
但 O(n^3) 的复杂度是不能被接受的,所以我们需要优化。
React 团队总结了以下两个规律,为将 O(n^3) 转换成 O(n) 确定了大前提:
除了这两个规律之外,还有一个规律,为 React 实现高效的 Diff 提供了灵感:DOM 节点之间的跨层级操作并不多,同层级操作是主流。
结合第三规律:同层级操作是主流。React 的 Diff 过程 直接放弃了跨层级的节点比较,它只针对相同层级的节点做对比。
如此一来,只需要从上到下做一次遍历,就可以完成整棵树的对比。
销毁 + 重建的代价是昂贵的,因此 不要做跨层级的操作,尽量保持 DOM 结构的稳定性。
本着主要矛盾的原则,React 任务,只有同类型的组件,才有进一步对比的必要性。
若 参与 Diff 的两个组件类型不同,那么直接放弃比较,原地替换掉旧节点。只有相同类型的组件,React 才会保留组件对应 DOM 树 或 子树,进行更深层次的遍历。
key 是用来帮助 React 识别哪些内容被更改、添加或删除。key 需要卸载用数组渲染出来的元素内部,并且需要赋予一个稳定的值。如果 key 发生了变更,React 则会直接触发重渲染。
解决的问题:同一层级下节点的重用问题。