首页 > 其他分享 >react中useTransition的执行原理

react中useTransition的执行原理

时间:2022-10-24 19:35:24浏览次数:48  
标签:lane 函数 useTransition update react hook 原理 执行 root

_版本:v18.2.0
本文为我花了大半年的时间潜心研究所写,转载请注明出处,谢谢

本文假设你读了本系列中的《触发onClick,react会执行哪些操作》、《react中suspense组件是如何重新渲染的》,基于此进行本文的分析研究

react中useTransition的执行原理

本例代码

useTransition在我看来是专门为Suspense组件而设计的,从源码层面可以看出其实现原理和Suspense组件或多或少有所关联
useTransition是将react的lane优先级机制从底层搬到api层面提供给用户使用的先例,在以前用户不需要关注react底层lane模型的运行机制,但是现在你需要了,哈哈
useTransition其实是使用较低优先级的lane去调度update,在startTransition函数中调用的所有hook都会生成一个较低优先级lane的update对象,在高优先级update未执行完成前是不会执行的

以此为示例解释useTransition是如何工作的,通过onClick事件触发setFlag
const [flag, setFlag] = useState('a')
const [isPending, startTransition] = useTransition()
<Suspense fallback={<div>Loading...</div>}>
  { flag === 'a' ? <Test1 /> : <Test2 /> }
</Suspense>
<button onClick={() => {
  startTransition(() => {
    setFlag('b')
  });
}}>切换</button>

概述

1. 当本例的函数组件进行初次渲染的时候,会执行useState和useTransition,构建初始的hook链表 hook_flag -> hook_pending -> hook_transition
2. 用户触发onClick事件,执行startTransition函数
  ** 在此函数中首先会触发hook执行setPending(true),从而调用dispatchSetState去创建一个update1对象,此时的lane为DiscreteEventPriority,将此对象添加到hook_pending中,随后执行ensureRootIsScheduled函数调度此对象
  ** 由于update1的lane为DiscreteEventPriority,所以直接调度一个微任务去执行flushSyncCallbacks
  ** 先为ReactCurrentBatchConfig$2.transition赋值,然后执行setPending(false)再次调用dispatchSetState去创建一个update2对象
    由于此时ReactCurrentBatchConfig$2.transition不为空,得到的lane为TransitionLane1,将此对象添加到hook_pending中,并且复用之前的调度
  ** 执行startTransition中的回调函数,在本例中就是执行setFlag('b'),调用dispatchSetState去创建一个update3对象
    此时的lane为update2对象的lane,也就是TransitionLane1,将此对象添加到hook_flag中,并且复用之前的调度
  ** 经过以上步骤处理后
    此时的hook链表为
      hook_flag -> hook_pending                  
        |               |
        |-->update3     |-->update1->update2
    此时的root.pendingLanes为DiscreteEventPriority | TransitionLane1
3. 执行步骤2调度的微任务,触发flushSyncCallback函数,首先从root.pendingLanes中分离出优先级更高的lane,也就是DiscreteEventPriority优先处理,随后执行renderRootSync遍历workInProgress树
  ** 当遍历workInProgress树下探到本例的函数组件时,执行updateFunctionComponent会引起此函数组件重新执行,从而更新hook
    首先处理useState('a'),调用updateState去执行updateReducer,获取update3.lane,此时为TransitionLane1,优先级不够需要跳过,重新构建baseQueue链表
    然后处理useTransition(),调用updateTransition,执行updateState,处理hook_pending中存储的update链表,由于update1.lane为DiscreteEventPriority,优先级足够不需要跳过直接更新状态值即可,但是update2需要跳过
    所以经过这步操作后,只有isPending变为true。协调此函数组件的孩子节点后,后续的下探处理逻辑和commit阶段的所有操作都是无用的,因为本例并没使用isPending
4. 执行完步骤3的commit阶段后,会再度执行ensureRootIsScheduled调度剩下的update,此时root.pendingLanes为TransitionLane1,本次调度的lane直接就是TransitionLane1
  ** 由于TransitionLane1的优先级比DiscreteEventPriority的低,所以需要先根据TransitionLane1获取其对应的事件优先级DefaultEventPriority,然后再根据NormalPriority去获取调度优先级NormalPriority
  ** 调度一个NormalPriority的callback在宏任务中执行performConcurrentWorkOnRoot
5. 执行步骤4调度的宏任务,执行performConcurrentWorkOnRoot,最终还是执行renderRootSync函数遍历workInProgress树
  ** 下探到本函数组件后,执行updateFunctionComponent,更新hook
    首先处理useState('a'),由于update3.lane为TransitionLane1,优先级足够直接更新状态值即可
    然后处理useTransition(),由于update2.lane为TransitionLane1,优先级足够直接更新状态值即可
    经过这步处理后,所有hook的update链表都处理完毕了,isPending变为false,flag变为'b',Suspense组件中的动态组件需要替换成<Test2 />
  ** 在后续的下探过程中,遇到Offscreen节点会去执行动态import去加载新的动态组件,详细步骤在本系列的《react中suspense组件是如何重新渲染的》中
  ** 在回溯过程中,遇到Suspense节点,会将“退出状态”置为RootSuspendedWithDelay
  ** 在回溯执行完毕后,会判断“退出状态”如果为RootSuspendedWithDelay,会继续判断当前处理的lane是否为Transition类型的,本例这里肯定是
    如果是,则直接结束执行,不会进入commit阶段,所产生的效果就是Suspense中的fallback不会渲染到页面,只会展示旧的动态组件
6. 后续的逻辑就是处理加载动态组件<Test2 />的promise的then语句中的代码,得到Module对象,再次调度一个NormalPriority优先级的宏任务,去渲染<Test2 />

初次执行函数组件中的hook

全局变量currentlyRenderingFiber$1存储当前workInProgress节点

useState -> mountState
1. hook = mountWorkInProgressHook()
  创建hook对象并且将其插入hook链表中,hook链表保存在fiber节点的memoizedState属性上
2. 如果useState传入的参数是函数则执行,得到返回值initialState
3. hook.memoizedState = hook.baseState = initialState 保存初始值
4. 创建queue对象
  queue = {
    pending: null,
    interleaved: null,
    lanes: NoLanes,
    dispatch: null,
    lastRenderedReducer: basicStateReducer,
    lastRenderedState: initialState 旧值
  }
  注:每一个hook都会有一个queue对象,每一次此hook调用都会创建一个update对象并且串联成update链表,此链表存在queue对象的interleaved属性上
5. hook.queue = queue
6. dispatch = queue.dispatch = dispatchSetState.bind(null, currentlyRenderingFiber$1, queue)
7. 返回 [hook.memoizedState, dispatch]

useTransition函数 -> mountTransition
1. const [isPending, setPending] = mountState(false) 
  从这里可以看出,调用useTransition实际上在hook链表中插入了两个节点,一个是useState的hook一个是useTransition的hook
2. start = startTransition.bind(null, setPending)
3. hook = mountWorkInProgressHook() 创建一个hook对象,并插入hook链表
4. hook.memoizedState = start
5. 返回 [isPending, start]

经过初次渲染后,hook链表大致如为 hook_flag -> hook_pending -> hook_transition

触发onClick事件执行startTransition

startTransition函数
1. 对比全局变量currentUpdatePriority和ContinuousEventPriority中较小的值,并且重新赋值给currentUpdatePriority
  从这一步就可以看出,startTransition触发的优先级比ContinuousEventPriority要高
  本例由onClick触发,所以这里优先级为DiscreteEventPriority
2. setPending(true) 调度一个微任务去执行flushSyncCallbacks
3. ReactCurrentBatchConfig$2.transition = { _updatedFibers = new Set() }
4. setPending(false)复用上一个调度
5. 执行startTransition传入的回调函数,也就是调用setFlag
6. transition._updatedFibers.clear()

触发hook

触发hook的调用链为
  onClick
    startTransition
      1. setPending(true) -> 调用dispatchSetState处理hook_pending
      2. setPending(false) -> 调用dispatchSetState处理hook_pending
      3. setFlag('b') -> 调用dispatchSetState处理hook_flag

*1* setPending(true)
  dispatchSetState(fiber, hook_pending.queue, action) 这里的fiber是currentlyRenderingFiber$1,action为true
  1. lane = requestUpdateLane(fiber) 获取优先级,这里肯定为DiscreteEventPriority
  2. 创建update对象
    update = {
      lane: lane,
      action: action,
      hasEagerState: false,
      eagerState: null,
      next: null
    }
  3. alternate = fiber.alternate
    判断 fiber.lanes为NoLanes && (alternate为空 || alternate.lanes为NoLanes) 如果为true
      刚执行第一个hook这里肯定为true,计算hook的初始值
      1. lastRenderedReducer = queue.lastRenderedReducer 此为basicStateReducer函数
      2. currentState = queue.lastRenderedState 旧值
      3. eagerState = lastRenderedReducer(currentState, action) 得到新值true
        触发basicStateReducer函数获取新值,typeof action === 'function' ? action(state) : action
      4. update.hasEagerState = true
      5. update.eagerState = eagerState
      6. 通过Object.is比较eagerState和currentState是否相同,这里肯定不同
  4. 执行enqueueConcurrentHookUpdate将update对象插入环形链表中,并且将本次update的lane合并到当前节点的lanes属性以及其祖先节点的childLanes属性
  5. 根据lane优先级,去执行scheduleUpdateOnFiber调度此update对象
  6. 执行entangleTransitionUpdate,此时内部逻辑未执行

*2* setPending(false)
  dispatchSetState(fiber, hook_pending.queue, action) 这里的fiber是currentlyRenderingFiber$1,action为false
  1. lane = requestUpdateLane(fiber) 获取优先级,这里处理后得到为TransitionLane1
  2. 同上
  3. 未执行
  4. 同上
  5. 同上,直接复用了当前的调度callback
  6. 执行entangleTransitionUpdate,将lane添加到root.entanglements数组中,并且将lane合并到root.entangledLanes

*3* setFlag('b')
  dispatchSetState(fiber, hook_flag.queue, action) 这里的fiber是currentlyRenderingFiber$1,action为'b'
  1. lane = requestUpdateLane(fiber) 获取优先级,这里处理后得到全局变量currentEventTransitionLane,其存储步骤*2*的lane,所以也是TransitionLane1
  2. 同上
  3. 未执行
  4. 同上
  5. 同上,直接复用了当前的调度callback
  6. 同上

调度update

scheduleUpdateOnFiber函数
1. root.pendingLanes |= lane
2. root.suspendedLanes = NoLanes
3. root.pingedLanes = NoLanes
4. 执行ensureRootIsScheduled

ensureRootIsScheduled函数
1. existingCallbackNode = root.callbackNode
2. 将“饥饿(已过期)”的lane添加到 expiredLanes 中
3. 调用getNextLanes获取nextLanes
4. newCallbackPriority = getHighestPriorityLane(nextLanes) 分离出优先级最高的lane
5. 比较root.callbackPriority和newCallbackPriority
  1. 如果相同,表明当前已经有相同lane的调度callback,直接复用即可,结束此函数后续执行
  2. 如果不同
    如果root.callbackNode不为空,表明当前有低优先级的调度callback正在执行,需要将其取消掉
    root.callbackNode.callback = null
6. 如果newCallbackPriority为SyncLane,此lane优先级最高
  1. 将performSyncWorkOnRoot.bind(null, root)放入syncQueue数组中
  2. 执行scheduleMicrotask(function () { flushSyncCallbacks() })调度一个微任务去执行flushSyncCallbacks
  3. newCallbackNode = null
7. 如果newCallbackPriority不为SyncLane,此lane为低优先级的,本例中TransitionLane1走这里
  1. 执行lanesToEventPriority获取lanes对应的事件优先级(本例为DefaultEventPriority),再根据此事件优先级获取对应的调度优先级 (本例为NormalPriority)
  2. 根据调度优先级,执行scheduleCallback调度一个callback,入宏任务队列
    newCallbackNode = scheduleCallback(NormalPriority, performConcurrentWorkOnRoot.bind(null, root))
8. root.callbackPriority = newCallbackPriority
9. root.callbackNode = newCallbackNode

getNextLanes函数
1. pendingLanes = root.pendingLanes
2. suspendedLanes = root.suspendedLanes
3. nonIdlePendingLanes = pendingLanes & NonIdleLanes
4. 如果nonIdlePendingLanes不为NoLanes
  1. nonIdleUnblockedLanes = nonIdlePendingLanes & ~suspendedLanes 移除suspendedLanes
  2. 如果nonIdleUnblockedLanes不为NoLanes
    走到这一步就可以看出,是分离出suspendedLanes,优先处理剩下的lane
    nextLanes = getHighestPriorityLanes(nonIdleUnblockedLanes)
    在getHighestPriorityLanes中对Transition类型的lane返回nonIdleUnblockedLanes & TransitionLanes,其他情况直接返回nonIdleUnblockedLanes
5. 如果root.entangledLanes
  ** 为NoLanes,返回nextLanes
  ** 不为NoLanes
    1. entanglements = root.entanglements
    2. lanes = nextLanes & root.entangledLanes
    3. while (lanes > 0)
      1. index = pickArbitraryLaneIndex(lanes) 获取lanes的最高位索引
      2. lane = 1 << index 获取lanes最高位的二进制数,其余低位全部置零
      3. nextLanes |= entanglements[index]
      4. lanes &= ~lane 最高位过滤,继续遍历

调度步骤
  调度update1
    update1 = {
      lane: DiscreteEventPriority,
      action: true,
      hasEagerState: true,
      eagerState: null,
      next: update2
    }
    root.pendingLanes |= DiscreteEventPriority
    调度一个微任务去执行flushSyncCallbacks
  调度update2
    update2 = {
      lane: TransitionLane1,
      action: false,
      hasEagerState: false,
      eagerState: null,
      next: update1
    }
    root.pendingLanes |= TransitionLane1
  调度update3
    update3 = {
      lane: TransitionLane1,
      action: 'b',
      hasEagerState: false,
      eagerState: null,
      next: update3
    }
    root.pendingLanes |= TransitionLane1

对应的链表为
  hook_flag -> hook_pending                  
    |               |
    |-->update3     |-->update1->update2

触发微任务

执行flushSyncCallbacks函数,遍历syncQueue数组执行其中存储的函数,本例为performSyncWorkOnRoot
performSyncWorkOnRoot函数
1. lanes = getNextLanes(root, NoLanes) 从root.pendingLanes中分离出优先级更高的lane
  由于此时的root.pendingLanes为 DiscreteEventPriority | TransitionLane1,所以分离出 DiscreteEventPriority
2. exitStatus = renderRootSync(root, lanes)

renderRootSync函数
1. 略去部分步骤,在本系列《触发onClick,react会执行哪些操作》中可以查到
  此时已经将concurrentQueues数组中queue的interleaved属性存储的update链表,转移至queue的pending属性上,并且将interleaved清空
2. 下探到本例的函数组件时,调用updateFunctionComponent,会重新执行函数组件从而更新hook,随后调用reconcileChildren进入协调阶段
  这一步会跳过update3和update2
3. 协调完本例的函数组件后,后续的下探处理逻辑和commit阶段的所有操作都是无用的,因为本例并没使用isPending
4. 继续执行ensureRootIsScheduled处理下一次调度,此时root.pendingLanes已经移除了DiscreteEventPriority,只剩下TransitionLane1
  此时调度一个NormalPriority优先级的宏任务去执行performConcurrentWorkOnRoot

更新hook

全局变量currentlyRenderingFiber$1存储当前workInProgress节点
更新hook的调用链为
  updateFunctionComponent
    1. useState('a') -> 调用updateState处理hook_flag,此时hook_flag中跳过了update3
    2. useTransition() -> updateTransition() -> updateState() 调用updateState处理hook_pending,此时hook_pending中跳过了update2
      页面中isPending此时为true

useState -> updateState -> updateReducer
1. hook = updateWorkInProgressHook() 更新currentlyRenderingFiber$1节点的hook
2. current = currentHook 此为current节点hook链表中的useState的hook对象
3. baseQueue = current.baseQueue
4. pendingQueue = hook.queue.pending 此为update链表
5. 将pendingQueue插入current.baseQueue尾部,重新连成环形链表,并且将hook.queue.pending置为null
6. 遍历current.baseQueue
  **此部分的代码为react的核心代码,能做到跳过低优先级任务**
  ---------
  第一次更新时,baseQueue链表为空的,经过上述步骤5将pending链表转移到baseQueue中了。如果update链表中有低优先级的update,则需要跳过,构建newBaseQueue
  假设此时的baseQueue为update_1(1) -> update_2(64) -> update_3(1) -> update_4(4),括号中的为此update的lane
  假设当前处理的lane为1
  遍历baseQueue
    1. 执行第一次遍历,遇到update_1他的lane为1,优先级足够,将newState赋值为update_1的新值
    2. 执行第二次遍历,遇到update_2他的lane为64,优先级不够需要跳过
      根据update_2复制一个新的update对象插入newBaseQueue中,并且newBaseState为newState
      此时newBaseQueue为 update_2(64)
    3. 执行第三次遍历,遇到update_3他的lane优先级足够,但是此时也要根据update_3复制一个新的update对象插入newBaseQueue中,并且newBaseState不重新赋值
      从这一步可以看出,只要遇到第一个跳过的update,其后面跟的所有update都需要复制一个,插入到newBaseQueue中。并且newBaseState始终为第一个跳过update的前一个update的新值
      此时newBaseQueue为 update_2(64) -> update_3(1)
    4. 执行第四次遍历,遇到update_4他的lane为4,优先级不够需要跳过
      此时newBaseQueue为 update_2(64) -> update_3(1) -> update_4(4)
  ---------
  1. 赋值
    first = baseQueue.next 头节点
    newState = current.baseState 旧值
    newBaseState = null
    newBaseQueueFirst = null
    newBaseQueueLast = null
    update = first
  2. updateLane = update.lane 此时为TransitionLane1
  3. 比较全局变量renderLanes & updateLane是否为updateLane,此时renderLanes为DiscreteEventPriority
    ** false,表明updateLane优先级不够,其对应的update在此次更新中跳过
      1. clone = {
          lane: updateLane,
          action: update.action,
          hasEagerState: update.hasEagerState,
          eagerState: update.eagerState,
          next: null
        }
      2. 如果newBaseQueueLast为空
        newBaseQueueFirst = newBaseQueueLast = clone
        newBaseState = newState
      3. 如果newBaseQueueLast不为空
        newBaseQueueLast = newBaseQueueLast.next = clone
      4. currentlyRenderingFiber$1.lanes = currentlyRenderingFiber$1.lanes | updateLane
    ** true,表明updateLane优先级足够,其对应的update放在此次更新
      1. newBaseQueueLast不为空
        1. _clone = {
            lane: NoLane,
            action: update.action,
            hasEagerState: update.hasEagerState,
            eagerState: update.eagerState,
            next: null
          }
        2. newBaseQueueLast = newBaseQueueLast.next = _clone
      2. update.hasEagerState为true
        newState = update.eagerState
      3. update.hasEagerState为false
        1. action = update.action
        2. newState = reducer(newState, action) 计算得出新值
  4. update = update.next 继续下次遍历
7. 如果newBaseQueueLast为空,表明没有update跳过
  newBaseState = newState
8. 如果newBaseQueueLast不为空,表明有update跳过
  newBaseQueueLast.next = newBaseQueueFirst 构建环形链表
9. 赋值
  hook.memoizedState = newState
  hook.baseState = newBaseState
  hook.baseQueue = newBaseQueueLast
  queue.lastRenderedState = newState
10. 返回[hook.memoizedState, queue.dispatch]

useTransition -> updateTransition
1. const [isPending] = updateState() 调用updateReducer处理hook_pending
  此时hook_pending中存储的queue链表的第一个元素update1的lane为DiscreteEventPriority,正好为此次更新的lane,而第二个元素update2为TransitionLane1需要跳过
  所以此时isPending为update1的新值也就是true
2. hook = updateWorkInProgressHook() 更新hook
3. start = hook.memoizedState
4. 返回 [isPending, start]

触发宏任务

performConcurrentWorkOnRoot函数
1. 调用getNextLanes获取lanes,本例为TransitionLane1
2. exitStatus = renderRootSync(root, lanes)

renderRootSync函数
1. 略去部分步骤,在本系列《触发onClick,react会执行哪些操作》中可以查到
2. 下探到本例的函数组件时,调用updateFunctionComponent,会重新执行函数组件从而更新hook,随后调用reconcileChildren进入协调阶段
  这一步会处理update3和update2,因为此时处理的lanes为TransitionLane1,而update3和update2的lane都为TransitionLane1,经过更新hook的处理后,flag值为'b',isPending值为false
3. 继续下探,遇到Offscreen节点调用updateOffscreenComponent,执行reconcileChildren对孩子节点协调过程中如果节点是lazy则会执行resolveLazy去加载lazy组件

完成后续的下探和回溯过程,在即将进入commit阶段时,会调用finishConcurrentRender函数
  在finishConcurrentRender函数中针对退出状态为RootSuspendedWithDelay的情况,会先执行includesOnlyTransitions判断此次处理的lanes是否是Transition类型的
  在本例中,走到这里肯定是Transition类型的,所以直接结束后续执行,未进入commit阶段。产生的效果就是,Suspense中渲染的旧节点不更新

后续的处理逻辑就是执行thenable.then中的函数,得到动态import返回的Module对象将其渲染到Suspense中,在本系列的《react中suspense组件是如何重新渲染的》中有详细介绍

思考

假如动态组件加载的时间过长达到了react中所谓的“饥饿(已过期)”怎么办?

回答此问题,首先要明白为什么会有这种问题出现
  因为startTransition中触发的hook都是低优先级的lane,靠后执行,如果在前面高优先级lane的update有很多的话,就容易产生延迟。
其次还要知道怎么判定是否“饥饿(已过期)”
  在执行ensureRootIsScheduled调度update时,会为当前update.lane计算对应的过期时间,并存储在root.expirationTimes中,
  在调度其他update时会比较root.pendingLanes记录的所有lane的过期时间和当前时间,如果过期了将其合并到root.expiredLanes

在调度update执行完成后,执行到performConcurrentWorkOnRoot函数,在此函数中会判断当前的root.expiredLanes是否包含lanes,如果包含则需要“时间分片”
  需要“时间分片”的,会执行renderRootConcurrent,此函数内部逻辑和renderRootSync大差不差
  renderRootConcurrent 执行 workLoopConcurrent
  renderRootSync 执行 workLoopSync
  function workLoopConcurrent() {
    while (workInProgress !== null && !shouldYield()) {
      performUnitOfWork(workInProgress);
    }
  }
  function workLoopSync() {
    while (workInProgress !== null) {
      performUnitOfWork(workInProgress);
    }
  }
  可以看出区别就在于shouldYield函数,在解释shouldYield函数之前需要了解清楚如下调用链

ensureRootIsScheduled
        ↓                                                                  
scheduleCallback -> schedulePerformWorkUntilDeadline -> performWorkUntilDeadline -> schedulePerformWorkUntilDeadline <--------|
                                                                  |                                                           |
                                                                  |-> flushWork -> workLoop -> 同当前时间比较(此处的当前时间会赋值给startTime),如果当前处理的task的expirationTime未超期
                                                                                      |
                                                                                      |-> 超期了,执行task.callback也就是performConcurrentWorkOnRoot
通过上述调用链可以看出,只有超期的task才会进入performConcurrentWorkOnRoot的执行,此时的startTime已经被赋值了,从这里开始执行到shouldYieldToHost函数如果时间间隔大于5证明当前js执行线程卡了
现在再看shouldYield函数的执行逻辑,可以看出当js线程被卡了,workLoopConcurrent中的下探遍历workInProgress的逻辑不执行,然后继续执行ensureRootIsScheduled函数,直到当前js执行线程不卡了才会处理下探遍历的逻辑
  var startTime = -1; 在执行performWorkUntilDeadline函数会为startTime赋值
  var frameInterval = 5; 这是一帧的时间间隔
  function shouldYieldToHost() {
    var timeElapsed = now() - startTime;
    if (timeElapsed < frameInterval) {
      return false;
    }
    return true;
  }

总结:
  在root.expiredLanes中的lane都是过期的lane,需要立即顺畅的执行更新,这个时候就不能出现js线程阻塞的问题。运用“时间分片”的逻辑,判断执行时间间隔如果超过5就为线程阻塞
  再度执行ensureRootIsScheduled形成一个递归的闭环,直到“时间分片”认为当前线程“空了”,才会处理workInProgress树的下探和回溯

通用函数

mountWorkInProgressHook 函数
1. 创建hook对象
  hook = {
    memoizedState: null,
    baseState: null,
    baseQueue: null,
    queue: null,
    next: null
  }
2. 全局变量workInProgressHook为空
  currentlyRenderingFiber$1.memoizedState = workInProgressHook = hook
3. 全局变量workInProgressHook不为空
  workInProgressHook = workInProgressHook.next = hook

requestUpdateLane 函数
1. isTransition = ReactCurrentBatchConfig$1.transition !== NoTransition
  ** 如果isTransition为true
    1. transition._updatedFibers.add(fiber)
    2. 如果全局变量currentEventTransitionLane为NoLane,其初始值就为NoLanes
      currentEventTransitionLane = claimNextTransitionLane()
    3. 返回currentEventTransitionLane
  ** 如果isTransition为false
    1. updateLane = currentUpdatePriority
    2. 返回 updateLane

enqueueConcurrentHookUpdate 函数
1. interleaved = queue.interleaved 存储update链表
2. 如果interleaved为空
  update.next = update
  queue存入全局变量concurrentQueues数组中
3. 如果interleaved不为空,将update插入interleaved环形链表中
  update.next = interleaved.next
  interleaved.next = update
4. queue.interleaved = update
5. 执行并返回markUpdateLaneFromFiberToRoot

markUpdateLaneFromFiberToRoot 函数
这里的fiber是currentlyRenderingFiber$1
1. fiber.lanes = fiber.lanes | lane
2. fiber.alternate.lanes = fiber.alternate.lanes | lane
3. 遍历fiber树,更新父节点的childLanes,操作同步骤1,2
4. 返回root节点

claimNextTransitionLane 函数
1. lane = nextTransitionLane 其中nextTransitionLane初始值为TransitionLane1
2. nextTransitionLane <<= 1 左移一位,如 '1111' -> '11110'
3. 如果nextTransitionLane & TransitionLanes为NoLanes,nextTransitionLane = TransitionLane1
4. 返回lane

entangleTransitionUpdate 函数
判断lane是否为Transition类型的lane(通过判断lane & TransitionLanes是否为NoLanes)
1. entangledLanes = hook.queue.lanes & lane | lane
1. hook.queue.lanes = entangledLanes
2. 执行markRootEntangled函数将entangledLanes添加到root.entanglements数组中

markRootEntangled 函数
1. rootEntangledLanes = root.entangledLanes |= entangledLanes
2. lanes = rootEntangledLanes
3. entanglements = root.entanglements
2. while (lanes > 0) 
  1. index = pickArbitraryLaneIndex(lanes) 获取lanes的最高位索引
  2. lane = 1 << index 获取lanes最高位的二进制数,其余低位全部置零
  3. 如果 lane & entangledLanes | entanglements[index] & entangledLanes 为true
    entanglements[index] |= entangledLanes
  4. lanes &= ~lane 最高位过滤,继续遍历

updateWorkInProgressHook 函数
1. 全局变量currentHook是否为空
  ** 是
    1. current = currentlyRenderingFiber$1.alternate
    2. current不为空,nextCurrentHook = current.memoizedState 存储这hook链表
  ** 否
    nextCurrentHook = currentHook.next
2. nextWorkInProgressHook = currentlyRenderingFiber$1.memoizedState
3. 如果nextWorkInProgressHook为空
  1. currentHook = nextCurrentHook
  2. 根据current节点上的hook创建一个新的hook
    newHook = {
      memoizedState: currentHook.memoizedState,
      baseState: currentHook.baseState,
      baseQueue: currentHook.baseQueue,
      queue: currentHook.queue,
      next: null
    }
  3. 将newHook插入到currentlyRenderingFiber$1.memoizedState链表中

标签:lane,函数,useTransition,update,react,hook,原理,执行,root
From: https://www.cnblogs.com/ye-hcj/p/16822501.html

相关文章

  • Redis Cluster 原理说的头头是道,这些不懂就是纸上谈兵
    RedisCluster集群相关配置,使用集群方式的你必须重视和知晓。别嘴上原理说的头头是道,而集群有哪些配置?如何配置让集群快到飞起,实现真正的高可用却一头雾水,通过下面这些配置......
  • Vue.nextTick核心原理
    相信大家在写vue项目的时候,一定会发现一个神奇的api,Vue.nextTick。为什么说它神奇呢,那是因为在你做某些操作不生效时,将操作写在Vue.nextTick内,就神奇的生效了。那这是什么......
  • rxjs Observable 设计原理背后的 Pull 和 Push 思路
    Observables顾名思义,是可以被观察的事务。在Rxjs的上下文里,Observable会随着时间的推移,在某个时间点产生数据。Observables可以:不停地(永远)产生值,比如interval操作......
  • 硬件加密芯片介绍 及 加密芯片选择(加密IC) 加密芯片原理
    前端时间有研究多款加密芯片,加密算法实现,以及破解可能,也有一些个人的观点,仅供参考; 一,加密芯片的来源及工作流程:市面上的加密芯片,基本都是基于某款单片机,使用I2C或SPI等......
  • React-hooks+TypeScript最佳实战
    ReactHooks什么是HooksReact一直都提倡使用函数组件,但是有时候需要使用state或者其他一些功能时,只能使用类组件,因为函数组件没有实例,没有生命周期函数,只有类组件才......
  • Vue 中 nextTick 的实现原理是什么
    vue中有一个较为特殊的API,nextTick。根据官方文档的解释,它可以在DOM更新完毕之后执行一个回调,用法如下://修改数据 vm.msg = 'Hello' //DOM还没有更新 Vue.......
  • 浏览器原理
     浏览器重绘与重排的区别?重排:部分渲染树(或者整个渲染树)需要重新分析并且节点尺寸需要重新计算,表现为重新生成布局,重新排列元素重绘:由于节点的⼏何属性发生改变或者......
  • React核心技术浅析
    1.JSX与虚拟DOM我们从React官方文档开头最基本的一段HelloWorld代码入手:ReactDOM.render(<h1>Hello,world!</h1>,document.getElementById('root'));这段代......
  • 测温仪器的原理知多少?
    测温仪器的原理知多少?测温枪也叫测温仪,这个东西有可能对我们来说都比较陌生,它主要是应用红外测温技术提供生产生活中的温度测量,所以又被称为红外测温枪。这项技术在产品质......
  • vue这些原理你都知道吗?(面试版)
    前言在之前面试的时候我自己也经常会遇到一些vue原理的问题,我也总结了下自己的经常的用到的,方便自己学习,今天也给大家分享出来,欢迎大家一起学习交流,有更好的方法......