1、setState机制
理想情况下:
setState是异步的,调用setState只会提交一次state修改到队列中,不会直接修改this.state。等到满足一定条件时,react会合并队列中的所有修改,触发一次update流程,更新this.state
由于setState会触发update过程,因此在update过程中必经的生命周期中调用setState会存在循环调用的风险,另外用于监听state更新完成,可以使用setState方法的第二个参数,回调函数。在这个回调中读取this.state就是已经批量更新后的结果
特殊情况下:
(1)mount流程中调用setState,不会进入update流程,队列在mount时合并修改并render
(2)setTimeout/Promise回调中调用setState,将不会进行队列的批更新,而是直接触发一次update流程
2、diff算法
三条策略:
(1)WebUI中DOM节点跨节点的操作特别少,可以忽略不计
(2)拥有相同类的组件会拥有相似的DOM结构。拥有不同类的组件会生成不同的DOM结构。
(3)同一层级的子节点,可以根据唯一的ID来区分
针对这三个策略,react diff实施的具体策略是:
(1)diff对树进行分层比较,只对比两棵树同级别的节点。跨层级移动节点,将会导致节点删除,重新插入,无法复用
(2)diff对组件进行类比较,类相同的递归diff子节点,不同的直接销毁重建。diff对同一层级的子节点进行处理时,会根据key进行简要的复用。两棵树中存在相同key的节点时,只会移动节点
另外,在对比同一层级的子节点时,diff算法会以新树的第一个子节点作为起点遍历新树,寻找旧树种与之相同的节点。如果节点存在,则移动位置,如果不存在,则新建一个节点。在这个过程中,维护了一个字段lastIndex,这个字段表示已遍历的所有新树子节点在旧树种最大的index。在移动操作时,只有旧的index小于lastIndex的才会移动
3、正确使用diff算法
(1)不适用跨层级移动节点的操作
(2)对于条件渲染多个节点时,尽量采用隐藏等方式切换节点,而不是替换节点
(3)尽量避免将后面的子节点移动到前面的操作,当节点数量较多时,会产生一定的性能问题
4、Fiber是什么?有什么用?
在React15版本的时候,如有组件要更新,则会递归向下遍历整个虚拟DOM树来判断需要更新的地方。这种递归的方式弊端在于无法中断,这要会造成如果我们需要更新一些庞大的组件,那么更新过程就会长时间阻塞主线程,从而造成用户的交互、动画的更新等都不能及时响应。
React组件更新过程简言之就是在持续调用函数的过程,这样的过程会形成一个虚拟的调用栈。加入能控制这个调用栈的执行,把整个更新任务拆解开,尽可能将更新任务放在浏览器空闲时间去执行,那么就能解决以上问题
Fiber重新实现了React的核心算法,他有能力将整个更新任务拆分为一个个小的任务,并且能控制这些任务的执行,主要包括两个核心技术:
(1)新的数据结构fiber
fiber被认为是一个工作单元,执行更新任务的整个流程(不包括渲染)就是在反复寻找工作单元并运行他们,实现拆分任务的功能。拆分成工作单元的目的就是为了能控制stack frame(调用栈中的内容),可以随时随地执行他们,由此使得我们在每运行一个工作单元后都可以按照情况继续执行或中断工作(中断的决定权在于调度器)。fiber内部存储了很多上下文信息,可以把它认为是改进版的虚拟DOM,同样也对应了组件实例及DOM元素。同时fiber也会组成fiber tree,但结构不再是一个树形,而是一个链表的结构
```
{
…
//浏览器环境下指DOM节点
stateNode: any,
//形成列表结构
return: Fiber | null,
child: Fiber | null,
sibling: Fiber | null,
//更新相关
pendingProps: any, //新的props
memoizedProps: any, //旧的props
//存储setState中的第一个参数
updateQueue: UpdateQueue | null,
memoizedState: any, //旧的state
//调度相关
expirationTime: ExpirationTime, //任务过期时间
//大部分情况下每个fiber都有一个替身fiber
//在更新过程中,所有操作都在替身上完成,当渲染完成后,替身会替代本身
alternate: Fiber | null,
//先简单任务是更新DOM相关的内容
effectTag: SideEffectTag, //指这个节点需要进行的DOM操作
//以下三个属性也会形成一个链表
nextEffect: Fiber | null, //下一个需要进行DOM操作的节点
firstEffect: Fiber | null, //第一个需要进行DOM操作的节点
lastEffect: Fiber | null, //最后一个需要进行DOM操作的节点,同时也可用于恢复任务
…
}
```
Fiber和fiber不是同一个概念。前者代表新的调和器,后者代表fiber node,也可以认为是改进后的虚拟DOM
(2)调度器
每次有新的更新任务发生的时候,调度器都会按照策略给这些任务分配一个优先级。通过这个优先级可以获取一个该更新任务必须执行的截止时间,优先级越高截止时间就越近,反之亦然。调度器通过实现requestIdleCallback函数来做到在浏览器空闲的时候去执行这些更新任务。简单的说,就是通过定时器的方式来获取每一帧的结束时间,得到每一帧的结束时间就能判断当下距离结束时间的差值。如果还未到结束时间,则可以继续执行更新任务,如果已经过了结束时间,那么当前帧已经没有时间执行任务,必须把执行权交还给浏览器,即打断任务的执行。另外当开始执行更新任务时,如果有新的更新任务进来,那么调度器就会按照两者的优先级大小来进行决策。如果新的任务优先级小,那么继续当下的任务,如果新的任务优先级大,那么会打断任务并开始新的任务