react 源码系列: 再看一次 renderWithHooks

「这是我参与11月更文挑战的第25天,活动详情查看:2021最后一次更文挑战

今天来的目标是分析 renderWithHooks 、bailoutHooks 是怎么实现的,这些内容不出意外就是捋顺 react 是怎么进行渲染更新的关键环节了。

renderWithHooks

突然意识到我之前似乎已经写过关于 renderWithHooks 的文章了,传送门。但是讲的也不完整,所以这里还是再补充一定内容。

  export function renderWithHooks<Props, SecondArg> (
    current: Fiber | null,
    workInProgress: Fiber,
    Component: (p: Props, arg: SecondArg) => any,
    props: Props,
    secondArg: SecondArg,
    nextRenderLanes: Lanes,
  ): any {}
复制代码

返回类型是 any ,这不能得到什么有效信息,但是没有关系,在实现代码中我们能看到它的返回值是最后一次调用Component(props, secondArg)的返回值。

我们给 renderWithHooks 传的 Component参数是 workInProgress.type,也就是用户定义的函数式组件函数。而我们分别记得在函数式组件定义中,我们返回的是用 jsx 语法定义的一些 xml 标签,这些 xml 标签会被编译成 React.createElement 函数。所以我们的 renderWithHooks 的返回值是 React.createElement的返回值,也就是以下这种结构的东东:

   const element = {
     // 这个取值表示是 element 
     $$typeof: REACT_ELEMENT_TYPE,
     // elment 的标签,比如 div, span
     type: type,
     key: key,
     ref: ref,
     props: props,
     // 负责创建该 element 的组件  
     _owner: owner,
   };
复制代码

current参数则是 workInProgress.alternate

Component 函数中,我们可能会用到 useEffect函数,它是怎么实现的?

对于更新阶段执行的 useEffect,执行链路是 useEffect -> updateEffect -> updateEffectImpl,实现如下:

 function updateEffectImpl (fiberFlags, hookFlags, create, deps): void {
   const hook = updateWorkInProgressHook()
   const nextDeps = deps === undefined ? null : deps
   let destroy = undefined
 ​
   if (currentHook !== null) {
     const prevEffect = currentHook.memoizedState
     destroy = prevEffect.destroy
     if (nextDeps !== null) {
       const prevDeps = prevEffect.deps
       if (areHookInputsEqual(nextDeps, prevDeps)) {
         hook.memoizedState = pushEffect(hookFlags, create, destroy, nextDeps)
         return
       }
     }
   }
 ​
   currentlyRenderingFiber.flags |= fiberFlags
 ​
   hook.memoizedState = pushEffect(
     HookHasEffect | hookFlags,
     create,
     destroy,
     nextDeps,
   )
 }
复制代码

其中的 pushEffect 返回的是一个 Effect 对象,结构如下:

 export type Effect = {|
   tag: HookFlags,
   create: () => (() => void) | void,
   destroy: (() => void) | void,
   deps: Array<mixed> | null,
   next: Effect,
 |}
复制代码

其中 destroy 回调就是 create 回调的返回值。 pushEffect 的实现逻辑也很简单,如下所示:

  1. 创建 effect 对象
  2. 检查 currentlyRenderingFiber.updateQueue,如果为null,则调用 createFunctionComponentUpdateQueue 为其实例化一个 updateQueue
  3. updateQueue 是一个循环单链表结构,存储最后一个Effect的指针 lastEffect,将effect 对象加入到链表尾部,更新 lastEffect 的指向

此时,我们清晰认识到了 fiber 的 updateQueue 存储的是用户用 useEffect 定义的副作用代码,那么就想关注这个 updateQueue 会在什么时候被执行,也许,它跟 reconcileChildren 有关。

reconcileChildren

它的作用是更新 workInProgress.child,过程中可能着副作用。

 export function reconcileChildren (
   current: Fiber | null,
   workInProgress: Fiber,
   nextChildren: any,
   renderLanes: Lanes,
 ) {
   if (current === null) {
     workInProgress.child = mountChildFibers(
       workInProgress,
       null,
       nextChildren,
       renderLanes,
     )
   } else {
     workInProgress.child = reconcileChildFibers(
       workInProgress,
       current.child,
       nextChildren,
       renderLanes,
     )
   }
 }
复制代码

reconcileChildFibers 签名如下

    function reconcileChildFibers(
      returnFiber: Fiber,
      currentFirstChild: Fiber | null,
      newChild: any,
      lanes: Lanes,
    ): Fiber | null {
复制代码

如果 newChild 是一个 key 为 null 的 fragment 节点(参考 Fragments-React说明),则令newChild = newChild.props.children;

如果 newChild 是一个非空 object,则根据newChild.$$typeof进行不同的操作,如果 newChild 是一个 element 节点( $$typeof = REACT_ELEMENT_TYPE),则函数返回 placeSingleChild 调用,而 placeSingleChild 的传参是一个 reconcileSingleElement 调用。

bailoutHooks

核心实现如下:

  export function bailoutHooks(
    current: Fiber,
    workInProgress: Fiber,
    lanes: Lanes,
  ) {
    workInProgress.updateQueue = current.updateQueue;
    workInProgress.flags &= ~(PassiveEffect | UpdateEffect);
    
    current.lanes = removeLanes(current.lanes, lanes);
  }
复制代码

也就是说,该函数的作用是更新 workInProgress 的 updateQueue、清除 PassiveEffect 和 UpdateEffect标记,并从 current.lanes 中移除 lanes。

我们回忆一下,在上一层也就是 updateFunctionComponent函数中是怎么调用这个 bailout 函数的?

首先,它是在执行完 renderWithHooks 函数之后、current 不为 null 且 didReceiveUpdate 为 false 的时候调用的,其次,它的调用形式如下:

  bailoutHooks(current, workInProgress, renderLanes)
复制代码

也就是说 workInProgress 的 alternate 上的 updateQueue 会同步到 workInProgress 上的 updateQueue,相当于由于没有接受update,则将 workInProgress.updateQueue还原为旧updateQueue了

\

Guess you like

Origin juejin.im/post/7035279682404614157