React16源码: React中调度之scheduleWork的源码实现

这篇具有很好参考价值的文章主要介绍了React16源码: React中调度之scheduleWork的源码实现。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

scheduleWork


1 ) 概述

  • ReactDOM.render, setState, forceUpdate 这几个方法最终都调用了 scheduleWork 这个方法

  • scheduleWork 当中,它需要去找到更新对应的 FiberRoot 节点

    • 在使用 ReactDOM.render 的时候,传给 scheduleWork 的就是 FiberRoot 节点
    • 在使用 setState, forceUpdate 的时候,传过去的都是某一个组件对应的 fiber 节点,而不是 FiberRoot
    • 这时候需要找到 FiberRoot 节点
  • 如果符合条件需要重置 stack

    • 这个 stack 里面有一些公共的变量
    • 这些公共变量在后续的调度和更新的过程中都非常的重要
    • 如果符合一定的条件,要重置这个stack
  • 如果符合条件就去请求工作调度

    • 注意,并不是所有情况都符合这个要求,需要进行工作调度
  • 拿之前的一个 Fiber Tree 节点来举例

              
                -----current-----> 
      FiberRoot                     RootFiber
                <---stateNode-----    |   
                                      |
                                    child  
                                      |
                                      ↓
                                      App
                                      |
                                    child
                                      |
                                      ↓
                                      |
                                      div
                                      / \
                                    /   \
                                child   child
                                  /       \
                                  /         \
                                /           \
                              Input-sibling-> List
                                |               \
                              child            child
                                |                \
                                ↓                 \
                              input               span --sibling--> span --sibling--> span --sibling--> button
    
    • 在上面这个 fiber tree 结构的 demo示例中,点击了最后这个 button 节点
    • 实际调用 setState 是我们写的这个 List 组件
    • 最终是把 RootFiber 这个 fiber 节点加入到调度队列当中
    • 而不是直接把我们的 List 它对应的 fiber 对象加入到调度队列当中
    • 每一次进入到调度队列的, 都是一个 RootFiber 这个对象, 不会是其他的
    • 因为更新的开始,也是从 RootFiber 开始的

2 )源码

定位到 packages/react-reconciler/src/ReactFiberScheduler.js文章来源地址https://www.toymoban.com/news/detail-787888.html

function scheduleWork(fiber: Fiber, expirationTime: ExpirationTime) {
  const root = scheduleWorkToRoot(fiber, expirationTime);
  if (root === null) {
    return;
  }

  if (
    !isWorking &&
    nextRenderExpirationTime !== NoWork &&
    expirationTime < nextRenderExpirationTime
  ) {
    // This is an interruption. (Used for performance tracking.)
    interruptedBy = fiber;
    resetStack();
  }
  markPendingPriorityLevel(root, expirationTime);
  if (
    // If we're in the render phase, we don't need to schedule this root
    // for an update, because we'll do it before we exit...
    !isWorking ||
    isCommitting ||
    // ...unless this is a different root than the one we're rendering.
    nextRoot !== root
  ) {
    const rootExpirationTime = root.expirationTime;
    requestWork(root, rootExpirationTime);
  }
  if (nestedUpdateCount > NESTED_UPDATE_LIMIT) {
    // Reset this back to zero so subsequent updates don't throw.
    nestedUpdateCount = 0;
    invariant(
      false,
      'Maximum update depth exceeded. This can happen when a ' +
        'component repeatedly calls setState inside ' +
        'componentWillUpdate or componentDidUpdate. React limits ' +
        'the number of nested updates to prevent infinite loops.',
    );
  }
}
  • 这个方法的接收的参数 fiber: Fiber, expirationTime: ExpirationTime
    • 第一个是 fiber对象
    • 第二个是 expirationTime,就是在创建更新的时候去计算出来的那个过期时间
  • 它一进来就调用了一个方法叫做 scheduleWorkToRoot
    function scheduleWorkToRoot(fiber: Fiber, expirationTime): FiberRoot | null {
      recordScheduleUpdate();
    
      if (__DEV__) {
        if (fiber.tag === ClassComponent) {
          const instance = fiber.stateNode;
          warnAboutInvalidUpdates(instance);
        }
      }
    
      // Update the source fiber's expiration time
      if (
        fiber.expirationTime === NoWork ||
        fiber.expirationTime > expirationTime
      ) {
        fiber.expirationTime = expirationTime;
      }
      let alternate = fiber.alternate;
      if (
        alternate !== null &&
        (alternate.expirationTime === NoWork ||
          alternate.expirationTime > expirationTime)
      ) {
        alternate.expirationTime = expirationTime;
      }
      // Walk the parent path to the root and update the child expiration time.
      let node = fiber.return;
      let root = null;
      if (node === null && fiber.tag === HostRoot) {
        root = fiber.stateNode;
      } else {
        while (node !== null) {
          alternate = node.alternate;
          if (
            node.childExpirationTime === NoWork ||
            node.childExpirationTime > expirationTime
          ) {
            node.childExpirationTime = expirationTime;
            if (
              alternate !== null &&
              (alternate.childExpirationTime === NoWork ||
                alternate.childExpirationTime > expirationTime)
            ) {
              alternate.childExpirationTime = expirationTime;
            }
          } else if (
            alternate !== null &&
            (alternate.childExpirationTime === NoWork ||
              alternate.childExpirationTime > expirationTime)
          ) {
            alternate.childExpirationTime = expirationTime;
          }
          if (node.return === null && node.tag === HostRoot) {
            root = node.stateNode;
            break;
          }
          node = node.return;
        }
      }
    
      if (root === null) {
        if (__DEV__ && fiber.tag === ClassComponent) {
          warnAboutUpdateOnUnmounted(fiber);
        }
        return null;
      }
    
      if (enableSchedulerTracing) {
        const interactions = __interactionsRef.current;
        if (interactions.size > 0) {
          const pendingInteractionMap = root.pendingInteractionMap;
          const pendingInteractions = pendingInteractionMap.get(expirationTime);
          if (pendingInteractions != null) {
            interactions.forEach(interaction => {
              if (!pendingInteractions.has(interaction)) {
                // Update the pending async work count for previously unscheduled interaction.
                interaction.__count++;
              }
    
              pendingInteractions.add(interaction);
            });
          } else {
            pendingInteractionMap.set(expirationTime, new Set(interactions));
    
            // Update the pending async work count for the current interactions.
            interactions.forEach(interaction => {
              interaction.__count++;
            });
          }
    
          const subscriber = __subscriberRef.current;
          if (subscriber !== null) {
            const threadID = computeThreadID(
              expirationTime,
              root.interactionThreadID,
            );
            subscriber.onWorkScheduled(interactions, threadID);
          }
        }
      }
    
      return root;
    }
    
    • 上面首先调用了 recordScheduleUpdate 这个方法是react当中的用来记录更新流程当中的时间
    • 这里涉及的东西比较多,先跳过,涉及一个独立的模块
    • DEV 的判断也跳过
    • 在这个方法中
      • 根据传入进来的 fiber 节点,它要去向上寻找,找到对应的 RootFiber 对象
      • 找的过程当中,进行一系列的操作
      • if (fiber.expirationTime === NoWork || fiber.expirationTime > expirationTime)
        • 当这个节点没有任何更新(没有设置过过期时间) 或者 已有更新产生未完成的任务的优先级 低于 当前的更新
        • 则将本身设置成优先级更高的 expirationTime
        • 这里主要任务是更新优先级
      • 第二步获取 alternate 并进行判断 if (alternate !== null && (alternate.expirationTime === NoWork || alternate.expirationTime > expirationTime))
        • 这很明显就是用来更新 alternate 的 expirationTime
        • 它其实跟上面更新 fiber.expirationTime 是一样的
      • 接下去,往上一层去找,比如 List 这个组件的 return 属性是 上一层的 div 组件
      • 进入判断 if (node === null && fiber.tag === HostRoot)
        • 注意,只有 RootFiber.return === null, 其他的都有上级Fiber
        • node === null 时,当前 fiber 就是 RootFiber, 同时,HostRoot 代表的就是 RootFiber
        • 这时候,root = fiber.stateNode
      • 如果不满足上述判断,则进入循环处理
        • whle循环,向上查找
          • node.childExpirationTime 是否是子树中优先级最高的
          • 如果不是高优先级,则更新成高优先级的
          • 同样对 alternate.childExpirationTime 的判断也是同样的更新
          • 接着,对 node.returnnode.tag 的判断是否是 RootFiber, 赋值 root
          • 如果不是,则 node = node.return 向上查找
      • 如果 root === null 的时候,则 return null
      • 下面的 if (enableSchedulerracing) 不涉及主流程,跳过
      • 最终 return root, 把 FiberRoot 返回
  • 获取 root 后,如果 root 是 null,则 return
  • 接着 if(!isWorking && nextRenderExpiration !== NoWork && expirationTime < nextRenderExpirationTime)
    • 如果没有在渲染更新的任务,并且,任务是异步任务没有执行完,并且新任务的优先级高于nextRenderExpirationTime 优先级
    • 这个意思是 新的高优先级任务打断了老的低优先级的任务,这是一个非常重要的特性
    • 它让我们可以优先执行高优先级的任务
    • interruptedBy 是一个被用来记录在哪里打断的变量值,这里可以不用太关注
    • 进入 resetStack()
      function resetStack() {
        // nextUnitOfWork 用来记录遍历整个子树的时候,执行到了哪个节点的更新, 即下一个即将要更新的节点
        // 这个判断如果是 true 则代表,之前的更新的是一个异步的任务,执行到一部分,由于时间片不够,把执行权交给浏览器
        // 这个值记录了下一个要执行的节点,如果现在没有优先级任务中断,它会回来继续执行 nextUnitOfWork 的任务更新
        if (nextUnitOfWork !== null) {
          // 指向上级,向上查找
          let interruptedWork = nextUnitOfWork.return;
          // 不断向上找被打断的任务,执行 unwindInterruptedWork
          while (interruptedWork !== null) {
            // 有了更高优先级的任务进来了,要进行更新,还是要从头开始更新,有存在的 nextUnitOfWork
            // 说明上层的几个组件 可能已经进行过更新了, 新的任务进来,再从头进行更新可能会导致前后的state并非这次想要更新的state
            // 会导致 state 错乱,在这里有 nextUnitOfWork 的情况,要把之前已经更新过的节点(上一个优先级任务对应的节点) 进行状态退回
            // 把状态退回到没有更新过的状态,再去执行优先级更高的任务,这块具体的先跳过
            unwindInterruptedWork(interruptedWork);
            interruptedWork = interruptedWork.return;
          }
        }
      
        if (__DEV__) {
          ReactStrictModeWarnings.discardPendingWarnings();
          checkThatStackIsEmpty();
        }
        // 设置公共变量的重置
        nextRoot = null;
        nextRenderExpirationTime = NoWork;
        nextLatestAbsoluteTimeoutMs = -1;
        nextRenderDidError = false;
        nextUnitOfWork = null;
      }
      
      
  • 接着,markPendingPriorityLevel
    • 这部分先跳过,涉及流程较多
  • 接着进入判断,if(!isWorking || isCommitting || nextRoot !== root) 是否要 requestWork
    • 当没有正在渲染更新,或正在提交,或不是 本root 节点(注:是针对多应用而言的,单应用只有一个root,这里很少会匹配)
      • 注意,isWorking 会包含 committing
      • committing 是第二阶段,是不可打断的阶段,把fiber树渲染完成后,更新到dom上的这个过程是 commiting 阶段
      • isCommitting 只有在中间执行的过程中才会是 true, 其他才会是false, 可以查看 commitRoot 函数
    • 符合条件,赋值 rootExpirationTime 并调用 requestWork 函数
      • 为何要重新读取 expirationTime, 因为之前调用过 markPendingPriorityLevel 可能导致
      • root.epirationTime 不一定和传入的 expirationTime 是一样的
  • 最后是涉及到 nestUpdateCount 的判断
    • 用来防止在组件更新的流程中,比如在 componentDidMount 中再次修改状态
    • 导致又进行了一次更新, 导致无限循环的处理,这里进行提醒

到了这里,关于React16源码: React中调度之scheduleWork的源码实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • React16源码: React中的updateHostRoot的源码实现

    HostRoot 的更新 1 )概述 HostRoot 是一个比较特殊的节点, 因为在一个react应用当中 它只会有一个 HostRoot , 它对应的 Fiber 对象是我们的 RootFiber 对象 重点在于它的更新过程 2 )源码 定位到 packages/react-reconciler/src/ReactFiberBeginWork.js#L612 HostRoot 创建更新的过程就是在 ReactFiberReconcile

    2024年01月22日
    浏览(44)
  • React16源码: React中的beginWork的源码实现

    beginWork 1 )概述 在 renderRoot 之后,要对我们的 Fiber 树每一个节点进行对应的更新 更新节点的一个入口方法,就是 beginWork 这个入口方法会有帮助我们去优化整棵树的更新过程 react 它的节点其实是非常多的,如果每一次子节点的一个更新 就需要每一个节点都执行一遍更新的话

    2024年01月20日
    浏览(47)
  • React16源码: React中的IndeterminateComponent的源码实现

    IndeterminateComponent 1 )概述 这是一个比较特殊的component的类型, 就是还没有被指定类型的component 在一个fibrer被创建的时候,它的tag可能会是 IndeterminateComponent 在 packages/react-reconciler/src/ReactFiber.js 中,有一个方法 createFiberFromTypeAndProps 中,一开始就声明了 在最终调用 createFibe

    2024年01月21日
    浏览(42)
  • React16源码: React中的reconcileChildren的源码实现

    reconcileChildren 1 )概述 在更新了一个节点之后,拿到它的props.children 要根据这个children里面的 ReactElement 来去创建子树的所有的 fiber 对象 要根据 props.children 来生成 fiber 子树,然后判断 fiber 对象它是否是可以复用的 因为我们在第一次渲染的时候,就已经渲染了整个 fiber 子树

    2024年01月20日
    浏览(41)
  • React16源码: React中的performWork的源码实现

    performWork 1 )概述 performWork 涉及到在调度完成,或者同步任务进来之后整个 root 节点链条如何更新 怎么更新一棵 Fiber 树,它的每一个节点是如何被遍历到,以及如何进行更新操作 A. 在执行 performWork 时候,是否有 deadline 的区分 deadline 是通过 reactschedule 它的一个时间片,更新

    2024年01月17日
    浏览(41)
  • React16源码: React中的unwindWork的源码实现

    unwindWork 1 )概述 在 renderRoot 的 throw Exception 里面, 对于被捕获到错误的组件进行了一些处理 并且向上去寻找能够处理这些异常的组件,比如说 class component 里面具有 getDerivedStateFromError 或者 componentDidCatch 这样的生命周期方法 这个class component 就代表它可以处理它的子树当中渲

    2024年01月25日
    浏览(37)
  • React16源码: React中的updateClassComponent的源码实现

    ClassComponent 的更新 1 ) 概述 在 react 中 class component,是一个非常重要的角色 它承担了 react 中 更新整个应用的API setState forceUpdate 在react当中,只有更新了state之后,整个应用才会重新进行渲染 在 class component 中, 它的逻辑相对复杂 2 )源码 在 packages/react-reconciler/src/ReactFiberB

    2024年01月21日
    浏览(40)
  • React16源码: React中的completeUnitOfWork的源码实现

    completeUnitOfWork 1 )概述 各种不同类型组件的一个更新过程对应的是在执行 performUnitOfWork 里面的 beginWork 阶段 它是去向下遍历一棵 fiber 树的一侧的子节点,然后遍历到叶子节点为止,以及 return 自己 child 的这种方式 在 performUnitOfWork 里面,还有一个方法叫做 completeUnitOfWork 在

    2024年01月23日
    浏览(44)
  • React16源码: React中的renderRoot的源码实现

    renderRoot 1 )概述 renderRoot 是一个非常复杂的方法 这个方法里处理很多各种各样的逻辑, 它主要的工作内容是什么? A. 它调用 workLoop 进行循环单元更新 遍历整个 Fiber Tree,把每一个组件或者 dom 节点对应的 Fiber 节点拿出来单一的进行更新,这是一个循环的操作 把整棵 Fiber T

    2024年01月17日
    浏览(43)
  • React16源码: React中的HostComponent & HostText的源码实现

    HostComponent HostText 1 )概述 HostComponent 就是我们dom原生的这些节点, 如: div, span, p 标签这种 使用的是小写字母开头的这些节点一般都认为它是一个 HostComponent HostText ,它是单纯的文本节点 主要关注它们的一个更新过程 2 )源码 定位到 packages/react-reconciler/src/ReactFiberBeginWork.js 进

    2024年01月24日
    浏览(41)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包