React 渲染机制
# 一、Diff
React 采用虚拟 DOM(VDOM)
每次属性(props)和状态(state)发生变化,render 函数会返回不同的元素树
React 会检测当前返回的元素树和上次渲染的元素树之间的差异
针对差异进行更新,最后渲染为真实 DOM
这就是整个 Reconciliation
过程,其核心是进行新旧 DOM 树对比的 diff 算法
# 二、shouldComponentUpdate
shouldComponentUpdate 是React生命周期中的一个, 会在 props 或 state 发生变化时触发(浅比较)
返回 true ,表示重新渲染,从而调用 render 函数
返回 false ,则不更新当前组件,可以控制 VDOM 树的 Diff 过程
上图是一个官方的实例,一个完成的 Recinciliation 过程
SCU 及 shouldComponentUpdate 的简写
红色节点表示 SCU 返回 true ,需要调用 render 进行新旧 VDOOM 树的 diff 过程
绿色节点表示 SCU 返回 false ,不需要进行 diff 更新
React 还推出了 PureComponent 这个 API,会在 props 或者 state 改变时对两者的数据进行浅比较
# 三、React 虚拟 DOM 的 Diff (过程)原理解析
React 虚拟 DOM 的 Diff 算法核心是它对于 边界情况和其他细节的处理
# 设计思想
从一个节点树参照另一颗节点树进行更新,常规做法是递归对每一个节点进行比较,算法的复杂度是 O (n^3), 1000个节点的树,要对比10亿次。
React 的 diff 能达到 O (n) 级别,它有一套自己方法论,简单叙述:
1.元素类型不同时
- 永远只比较同层节点,不会跨层级比较节点
- 不同的两个节点产生不同的树,把该节点及其后代元素全部干掉,替换成新的
- 通过 key 值制定那些元素是相同的
2.元素类型相同时
- 都是DOM节点时
React只会修改 DOM 元素上的 className 属性和 title 属性,然后对子节点进行递归处理
<div className="old" title="老节点" />
<div className="new" title="新节点" />
- 都是组件元素
组件实例保持不变,更新props。
这时会触发 componentWillReceiveProps()
然后通过 shouldComponentUpdate 返回值决定是否 render
- 特殊情况:遍历子元素
React 引入 key 值的概念,遍历子元素时,都需要增加一个 key
作用是提升效率,快速定位到元素进行 update。
<ul>
<li key="third">3</li>
<li key="first">1</li>
<li key="second">2</li>
</ul>
key 的选取:不需要全局唯一,但必须保持列表唯一
非常多的人喜欢用数组下标作为 key 值,在元素顺序不改变的情况是没有问题的,一旦顺序发生改变,diff性能极具下降
由于 F 的插入,后面的 C、D、E 索引值都改变,即 key 值改变,因此后面的节点都得更新。
而且,数组乱序或者在头部插入都会导致同样的更新问题。
因此,不用数组索引做 key 的原因在于:数组下标值不稳定,修改顺序会修改当前 key