问题1
1 | // 2 3 4 5 7 6 1(当前调度未超出时间切片) |
问题2
1 | // 把setTimeout放在render之前的执行顺序 |
问题3
从源码来看,effect
的 create
函数是在一个被调度的 callback
里面去执行的,为什么它还是会比 Promise.then()
的 callback
函数先执行?
useEffect
是在 commit
阶段被 scheduler
加入到 taskQueue
中的,一个时间切片(5ms)内,如果时间允许,可能会执行很多个 task
。
taskQueue
中的任务又是在宏任务中被执行的,先于微任务。
总之,当前时间切片时间允许就是 effect
在 Promise.then()
前,如果时间不够 effect
在 Promise.then()
后。
问题4
如果中间加了个sleep呢?如果不加呢?
useEffect
是在 commit
阶段被 scheduler
加入到 taskQueue
中的。
- 加sleep,导致超出时间切片5ms,
taskQueue
中的调度就要等到下一个宏任务再去调度 - 不加sleep,可能在5ms之内,
useEffect
的调度就被执行了,也有可能有各种高优先级的任务在useEffect
前导致以后得调度再执行useEffect
1 |
|
问题5
5.1 刷新页面,console.log打印了几次?
1 | import { useState, useEffect } from 'react' |
答案:3次。
第一次:函数组件初次渲染,执行log函数,并且此次由于useEffect函数执行,在fiber节点上记录了包含create函数的effect对象;
第二次:源于组件初次渲染完成之后,延迟useEffect的create函数,此时create函数中执行的是setState事件。setState导致函数组件更新,那么再次执行函数组件这个函数,log再次打印。并且此次又记录了create函数的effect对象。
第三次:基本同第二次,不同的地方在于第三次的时候,前后两次状态值相同,函数组件检测到没有更新发生,bailout了。
详细过程:
- 初始化执行UseABC组件函数,flush -> workloop -> performConcurrentWorkOnRoot,进到render阶段,在当前fiber的updateQueue上记录了要更新的effect,打印一次log
- 初始化到了commit阶段,通过 scheduleCallback 向 taskQueue 加入一个优先级为 NormalPriority 的 flushPassiveEffects 任务
- /* commit的最后还是会ensureRootIsScheduled调度一次微任务
scheduleImmediateTask(processRootScheduleInMicrotask);
或者performConcurrentWorkOnRoot的最后调度scheduleTaskForRootDuringMicrotask是否要加入高优先级任务,可能涉及到其它高优先级任务插队执行的问题,在flushWork结束的finally里判断如果有任务再次发起宏任务调度 */
- /* commit的最后还是会ensureRootIsScheduled调度一次微任务
- 不考虑高优先级任务插队或者其他各种情况的话,正常执行完毕后回到 workloop 中,会取出task 或者在下次切片中取出task执行,所以 flushPassiveEffects 任务得以执行,在从root开始遍历,执行各个fiber上的updateQueue的effect
- 执行到setCount1时候,等于执行dispatchSetState.bind(null,currentlyRenderingFiber,queue),相当于增加了一个调度更新scheduleUpdateOnFiber,cheduleUpdateOnFiber执行时会标记root上的更新也会把对应的update添加到对应fiber上,最后通过 ensureRootIsScheduled 发起微任务调度,但是commit已经发起过了,就不重复发起了,最终是通过微任务把setCount1加入到了taskQueue中
- 不管中间发生了什么,当再次执行与setCount1任务相同优先级的调度时候,从root开始遍历,再次执行UseABC组件函数,count1变成了0,且再打印一次log(执行UseABC组件函数的过程中又遇到了useEffect执行setCount1,又添加了调度更新)
- 当再次执行与此次任务相同优先级的调度时候,那就再次同上执行UseABC组件函数,再打印一次log,但是不同的是,现在count1的值没变化,bailout了
5.2 如果useEffect依赖为[],console.log打印了几次?
答案:2次
- 第一次:函数组件初次渲染,执行log函数,并且此次由于useEffect函数执行,在fiber节点上记录了包含create函数的effect对象;
- 第二次:源于组件初次渲染完成之后,延迟useEffect的create函数,此时create函数中执行的是setState事件。setState导致函数组件更新,那么再次执行函数组件这个函数,log再次打印。但是此次useEffect没有变化,故不再后续执行。