Appearance
React 的页面渲染
React 的渲染流程
JSX -> 虚拟 DOM -> 真实 DOM
React 的更新流程
React 的更新流程可以分为以下 6 部
- props/state 改变
- render 函数重新执行
- 产生新的 DOM 树
- 更新到真实的 DOM
- 计算出差异进行更新
- 新旧 DOM 树进行 diff
假设每一次的变化就是一棵树
React 在 props 或 state 发生改变时,会调用 React 的 render 方法,会创建一颗不同的树。
需要基于这两颗不同的树之间的差别来判断如何有效的更新 UI
如果一棵树参考另外一棵树进行完全比较更新,那么即使是最先进的算法,该算法的复杂程度为 O(n 3 ),其中 n 是树中元素 的数量;
- https://grfia.dlsi.ua.es/ml/algorithms/references/editsurvey_bille.pdf;
- 如果在 React 中使用了该算法,那么展示 1000 个元素所需要执行的计算量将在十亿的量级范围;
- 这个开销太过昂贵了,React 的更新性能会变得非常低效;
优化更新树
React 对这个算法进行了优化,将其优化成了 O(n),如何优化的呢?
- 同层节点之间相互比较,不会垮节点比较;
- 不同类型的节点,产生不同的树结构;
- 开发中,可以通过 key 来指定哪些节点在不同的渲染下保持稳定;
对比不同类型的元素
当节点为不同的元素,React 会拆卸原有的树,并且建立起新的树:
- 当一个元素从
<a>变成<img>,从<Article>变成<Comment>,或从<Button>变成<div>都会触发一个完整的重建 流程; - 当卸载一棵树时,对应的 DOM 节点也会被销毁,组件实例将执行 componentWillUnmount() 方法;
- 当建立一棵新的树时,对应的 DOM 节点会被创建以及插入到 DOM 中,组件实例将执行 componentWillMount() 方法, 紧接着 componentDidMount() 方法;
React 会销毁 Counter 组件并且重新装载一个新的组件,而不会对 Counter 进行复用;
jsx
// 例如一下代码更新
<div>
<Counter/>
</div>
<div>
<Counter/>
</div>
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
对比同一类型的元素
当比对两个相同类型的 React 元素时,React 会保留 DOM 节点,仅比对及更新有改变的属性。
- 通过比对这两个元素,React 知道只需要修改 DOM 元素上的 className 属性
jsx
// 例如一下代码更新
<div className="before" title="stuff"/>
<div className="after" title="stuff"/>
1
2
3
2
3
- 当更新 style 属性时,React 仅更新有所更变的属性。
- 通过比对这两个元素,React 知道只需要修改 DOM 元素上的 color 样式,无需修改 fontWeight。
jsx
// 例如一下代码更新
<div className="before" title="stuff"/>
<div className="after" title="stuff"/>
1
2
3
2
3
- 如果是同类型的组件元素
- 组件会保持不变,React 会更新该组件的 props,并且调用 componentWillReceiveProps() 和 componentWillUpdate() 方 法;
- 下一步,调用 render() 方法,diff 算法将在之前的结果以及新的结果中进行递归;
对子节点进行递归
在默认条件下,当递归 DOM 节点的子元素时,React 会同 时遍历两个子元素的列表;当产生差异时,生成一个 mutation。
- 我们来看一下在最后插入一条数据的情况:
- 前面两个比较是完全相同的,所以不会产生 mutation;
- 最后一个比较,产生一个 mutation,将其插入到新的 DOM 树中即可;
但是如果我们是在中间插入一条数据
- React 会对每一个子元素产生一个 mutation,而不是保 持
- wjw 和
- cx 的不变;
- 这种低效的比较方式会带来一定的性能问题;
jsx
// 初始化
<ul>
<li>wjw<li/>
<li>cx<li/>
<ul/>
// 第一种情况
<ul>
<li>wjw<li/>
<li>cx<li/>
<li>ylp<li/>
<ul/>
// 第二种情况
<ul>
<li>wjw<li/>
<li>ylp<li/>
<li>cx<li/>
<ul/>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
keys 的优化
所以我们在遍历列表的时候,需要我们加入了一个 key,如果不加入,就会报错
text
Warning: Each child in a List should have a unique "key" prop.
1
在最后位置插入数据
就如上面的情况 1,这时候 key 的作用不大
前面插入数据
在没有 key 的情况下,所有的 li 都需要进行修改
当插入中间的时候
当子元素(这里的 li) 拥有 key 时,React 使用 key 来匹配原有树上的子元素以及最新树上的子元素:
- 在下面这种场景下,key 为 111 和 222 的元素仅仅进行位移,不需要进行任何的修改;
- 将 key 为 333 的元素插入到最前面的位置即可;
key 的重要性
通过以上的描述,可以看出 key 是作为一个标识来进行操作,所以在使用的时候要注意
- key 应该是唯一的;
- key 不要使用随机数(随机数在下一次 render 时,会重新生成一个数字);
- 使用 index 作为 key,对性能是没有优化的
render 函数的调用
通过以上的描述,可以看出,所有的组件其实都是经过了 render 才会渲染到页面上去,而页面来说每一个组件,其实不是每一次都需要 render。 从 key 的优化来说,比如说我们修改了一个 App 的数据,如果所有的组件,都重新 render,进行 diff 算法,那么性能就很低 而很多组件对于一个数据改变,也并不需要重新 render 他们是通过 state、props 发生了变化之后,再调用自己的 render 方法
如何来控制 render 方法是否被调用呢?