React16源码: React中的beginWork的源码实现

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

beginWork


1 )概述

  • 在 renderRoot 之后,要对我们的 Fiber 树每一个节点进行对应的更新
  • 更新节点的一个入口方法,就是 beginWork
  • 这个入口方法会有帮助我们去优化整棵树的更新过程
    • react 它的节点其实是非常多的,如果每一次子节点的一个更新
    • 就需要每一个节点都执行一遍更新的话,它整体的性能肯定会不好,而且是没有必要的
    • 我们一个子节点的更新,可能不会影响到它的兄弟节点的更新
    • 所以这部分肯定是要优化的
  • 具体它是如何进行优化的,如下
    • 首先,要判断组件的更新是否可以优化
    • 然后要根据节点的类型分发进行处理,每一个节点它的更新方式会不一样
    • 最后是要根据 expirationTime 等信息判断这个节点的更新是否可以跳过

2 )源码

  • 在 renderRoot 中,它里面调用一个方法叫做 workLoop
  • 在 workLoop 中调用的一个方法叫做 performUnitOfWork
  • 而 beginWork 方法就在 performUnitOfWork 中调用
  • beginWork 就是执行对整棵树的每一个节点进行更新的一个操作
  • beginWork 来自于
    import {beginWork} from './ReactFiberBeginWork';
    

定位到 packages/react-reconciler/src/ReactFiberBeginWork.js
整个文件 export 出去的只有 beginWork 方法文章来源地址https://www.toymoban.com/news/detail-806810.html

function beginWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderExpirationTime: ExpirationTime,
): Fiber | null {
  // 读取了 workInProgress.expirationTime 这个值,这个 expirationTime 是节点上的 expirationTime
  // 函数第三个参数 renderExpirationTime 是 nextExpirationTimeToWorkOn 和 下面这个 expirationTime 有区别
  const updateExpirationTime = workInProgress.expirationTime;
  // 在 ReactDOM.render 第一次渲染时,第一个节点 current 是有值的, 接下去的节点都是没有的
  // 因为我们操作都是在 workInProgress 上操作的
  // 所以,workInProgress 创建的 child 节点也是一个 workInProgress 而不是 current
  // performUnitOfWork 接收的就是 workInProgress
  if (current !== null) {
    const oldProps = current.memoizedProps;
    const newProps = workInProgress.pendingProps;
    // 下面 renderExpirationTime 标志优先级最大都到哪个点,如果节点上的 expirationTime 比 renderExpirationTime 小
    // 说明节点上有优先级更高的任务,在这次渲染里是会执行的,反之,则不需要进行渲染,这个节点可以跳过
    // (updateExpirationTime === NoWork || updateExpirationTime > renderExpirationTime) 代表没有更新 或 有更新,但是优先级不高
    // hasLegacyContextChanged 属 context 相关api, childContextType 先跳过
    // oldProps === newProps 表示 props 一样
    if (
      oldProps === newProps &&
      !hasLegacyContextChanged() &&
      (updateExpirationTime === NoWork ||
        updateExpirationTime > renderExpirationTime)
    ) {
      // This fiber does not have any pending work. Bailout without entering
      // the begin phase. There's still some bookkeeping we that needs to be done
      // in this optimized path, mostly pushing stuff onto the stack.
      // switch 先跳过
      switch (workInProgress.tag) {
        case HostRoot:
          pushHostRootContext(workInProgress);
          resetHydrationState();
          break;
        case HostComponent:
          pushHostContext(workInProgress);
          break;
        case ClassComponent: {
          const Component = workInProgress.type;
          if (isLegacyContextProvider(Component)) {
            pushLegacyContextProvider(workInProgress);
          }
          break;
        }
        case HostPortal:
          pushHostContainer(
            workInProgress,
            workInProgress.stateNode.containerInfo,
          );
          break;
        case ContextProvider: {
          const newValue = workInProgress.memoizedProps.value;
          pushProvider(workInProgress, newValue);
          break;
        }
        case Profiler:
          if (enableProfilerTimer) {
            workInProgress.effectTag |= Update;
          }
          break;
        case SuspenseComponent: {
          const state: SuspenseState | null = workInProgress.memoizedState;
          const didTimeout = state !== null && state.didTimeout;
          if (didTimeout) {
            // If this boundary is currently timed out, we need to decide
            // whether to retry the primary children, or to skip over it and
            // go straight to the fallback. Check the priority of the primary
            // child fragment.
            const primaryChildFragment: Fiber = (workInProgress.child: any);
            const primaryChildExpirationTime =
              primaryChildFragment.childExpirationTime;
            if (
              primaryChildExpirationTime !== NoWork &&
              primaryChildExpirationTime <= renderExpirationTime
            ) {
              // The primary children have pending work. Use the normal path
              // to attempt to render the primary children again.
              return updateSuspenseComponent(
                current,
                workInProgress,
                renderExpirationTime,
              );
            } else {
              // The primary children do not have pending work with sufficient
              // priority. Bailout.
              const child = bailoutOnAlreadyFinishedWork(
                current,
                workInProgress,
                renderExpirationTime,
              );
              if (child !== null) {
                // The fallback children have pending work. Skip over the
                // primary children and work on the fallback.
                return child.sibling;
              } else {
                return null;
              }
            }
          }
          break;
        }
      }
      // 这个方法跳过该节点与其所有子节点的更新
      return bailoutOnAlreadyFinishedWork(
        current,
        workInProgress,
        renderExpirationTime,
      );
    }
  }
  
  // Before entering the begin phase, clear the expiration time.
  workInProgress.expirationTime = NoWork;
  // 如果节点有更新,通过节点的 tag,执行不同的方法,进行组件的更新
  switch (workInProgress.tag) {
    case IndeterminateComponent: {
      const elementType = workInProgress.elementType;
      return mountIndeterminateComponent(
        current,
        workInProgress,
        elementType,
        renderExpirationTime,
      );
    }
    case LazyComponent: {
      const elementType = workInProgress.elementType;
      return mountLazyComponent(
        current,
        workInProgress,
        elementType,
        updateExpirationTime,
        renderExpirationTime,
      );
    }
    case FunctionComponent: {
      const Component = workInProgress.type;
      const unresolvedProps = workInProgress.pendingProps;
      const resolvedProps =
        workInProgress.elementType === Component
          ? unresolvedProps
          : resolveDefaultProps(Component, unresolvedProps);
      return updateFunctionComponent(
        current,
        workInProgress,
        Component,
        resolvedProps,
        renderExpirationTime,
      );
    }
    case ClassComponent: {
      const Component = workInProgress.type;
      const unresolvedProps = workInProgress.pendingProps;
      const resolvedProps =
        workInProgress.elementType === Component
          ? unresolvedProps
          : resolveDefaultProps(Component, unresolvedProps);
      return updateClassComponent(
        current,
        workInProgress,
        Component,
        resolvedProps,
        renderExpirationTime,
      );
    }
    case HostRoot:
      return updateHostRoot(current, workInProgress, renderExpirationTime);
    case HostComponent:
      return updateHostComponent(current, workInProgress, renderExpirationTime);
    case HostText:
      return updateHostText(current, workInProgress);
    case SuspenseComponent:
      return updateSuspenseComponent(
        current,
        workInProgress,
        renderExpirationTime,
      );
    case HostPortal:
      return updatePortalComponent(
        current,
        workInProgress,
        renderExpirationTime,
      );
    case ForwardRef: {
      const type = workInProgress.type;
      const unresolvedProps = workInProgress.pendingProps;
      const resolvedProps =
        workInProgress.elementType === type
          ? unresolvedProps
          : resolveDefaultProps(type, unresolvedProps);
      return updateForwardRef(
        current,
        workInProgress,
        type,
        resolvedProps,
        renderExpirationTime,
      );
    }
    case Fragment:
      return updateFragment(current, workInProgress, renderExpirationTime);
    case Mode:
      return updateMode(current, workInProgress, renderExpirationTime);
    case Profiler:
      return updateProfiler(current, workInProgress, renderExpirationTime);
    case ContextProvider:
      return updateContextProvider(
        current,
        workInProgress,
        renderExpirationTime,
      );
    case ContextConsumer:
      return updateContextConsumer(
        current,
        workInProgress,
        renderExpirationTime,
      );
    case MemoComponent: {
      const type = workInProgress.type;
      const unresolvedProps = workInProgress.pendingProps;
      const resolvedProps = resolveDefaultProps(type.type, unresolvedProps);
      return updateMemoComponent(
        current,
        workInProgress,
        type,
        resolvedProps,
        updateExpirationTime,
        renderExpirationTime,
      );
    }
    case SimpleMemoComponent: {
      return updateSimpleMemoComponent(
        current,
        workInProgress,
        workInProgress.type,
        workInProgress.pendingProps,
        updateExpirationTime,
        renderExpirationTime,
      );
    }
    case IncompleteClassComponent: {
      const Component = workInProgress.type;
      const unresolvedProps = workInProgress.pendingProps;
      const resolvedProps =
        workInProgress.elementType === Component
          ? unresolvedProps
          : resolveDefaultProps(Component, unresolvedProps);
      return mountIncompleteClassComponent(
        current,
        workInProgress,
        Component,
        resolvedProps,
        renderExpirationTime,
      );
    }
    default:
      invariant(
        false,
        'Unknown unit of work tag. This error is likely caused by a bug in ' +
          'React. Please file an issue.',
      );
  }
}
  • 注意,最顶层 workInProgress.expirationTime; 这个 expirationTime Fiber 节点上的 expirationTime
    • 对比 ReactFiberScheduler.js 中的 renderRoot 中读取的 root.nextExpirationTimeToWorkOn
    • 这个 root 是 FiberRoot, 而 FiberRoot 对应的 Fiber对象是 RootFiber
    • RootFiber 是 FiberRoot 的 current 属性,它没有对应React组件树的任何节点, 它只对应 FiberRoot 节点
      • FiberRoot 节点也就是挂载整个应用到的 Dom 节点
      • FiberRoot 上面存储整个应用的 expirationTime 和
    • RootFiber 的 stateNode 属性是 FiberRoot
    • FiberRoot 上面会存储整个应用上面的 expirationTime 和 nextExpirationTimeToWorkOn
    • 上面的两个值意义是不一样的, 在 renderRoot 函数中的一段代码如下
      // Reset the stack and start working from the root.
      resetStack();
      nextRoot = root;
      nextRenderExpirationTime = expirationTime;
      // nextUnitOfWork 是一个Fiber对象, 对应 RootFiber, 而非 FiberRoot
      // 更新过程使用的都是 Fiber 对象,不会是 FiberRoot
      nextUnitOfWork = createWorkInProgress(
        nextRoot.current, // 注意这里
        null,
        nextRenderExpirationTime,
      );
      
      • nextUnitOfWork 才是我们每一个节点去更新时要操作的节点对象
    • 所以,workInProgress.expirationTime 是对应Fiber节点产生更新的过期时间
      • 这里页面点击按钮让某个组件更新,最多到App上面创建更新
      • 不可能到 RootFiber 上面创建更新
    • 而 RootFiber 产生更新时在 ReactDOM.render 的时候才会有
    • 注意,在 beginWork 第一个参数是 current 是 Fiber对象,它对应的tag是 HostRoot
      • 在创建 FibeRoot 时, 在 ReactFiberReconciler.js 中的 createContainer 中
      • 有一个方法叫做 createFiberRoot, 定位到 ReactFiberRoot.js 中
      • 定位到 createHostRootFiber 方法,而这个方法又来自于 ReactFiber.js
      • 找到 createFiber, 是最终创建Fiber对象的时候调用的方法
        return createFiber(HostRoot, null, null, mode);
        
      • 参数是 HostRoot, null, null, mode
      • 第一个参数 HostRoot 就是 Fiber的tag
    • 在 ReactFiberScheduler.js 中 performUnitOfWork 接收的就是一个 workInProgress
      • 它传给 beginWork 的第一个参数 current 是 workInProgress.alternate
      • 在第一次渲染,也就是 ReactDOM.render 的时候, workInProgress 是刚刚创建的
      • 是不会在创建一个 current, 要等渲染结束后,把 current 和 workInProgress 的指针进行一个调换
      • 它才会变成有 current, 没有 workInProgress,再下次有更新产生,进行渲染时,才会创建一个新的 workInProgress
      • 这时候,current 和 workInProgress 都有了
      • 在最顶层 判断 current 是否 为 null, 也就是 判断是否是第一次渲染
  • 关于 bailoutOnAlreadyFinishedWork 这个方法帮助跳过该节点与其所有子节点的更新
    function bailoutOnAlreadyFinishedWork(
      current: Fiber | null,
      workInProgress: Fiber,
      renderExpirationTime: ExpirationTime,
    ): Fiber | null {
      cancelWorkTimer(workInProgress);
    
      if (current !== null) {
        // Reuse previous context list
        workInProgress.firstContextDependency = current.firstContextDependency;
      }
    
      if (enableProfilerTimer) {
        // Don't update "base" render times for bailouts.
        stopProfilerTimerIfRunning(workInProgress);
      }
    
      // Check if the children have any pending work.
      const childExpirationTime = workInProgress.childExpirationTime; // 这个值是 React 16.5 加上的,更早跳过更新
      // 子树上无更新,或高优先级任务在这次渲染中完成的 则跳过,是一个非常大的优化
      if (
        childExpirationTime === NoWork ||
        childExpirationTime > renderExpirationTime
      ) {
        // The children don't have any work either. We can skip them.
        // TODO: Once we add back resuming, we should check if the children are
        // a work-in-progress set. If so, we need to transfer their effects.
        return null;
      } else {
        // This fiber doesn't have work, but its subtree does. Clone the child
        // fibers and continue.
        // 如果子树上有更新要执行,拷贝老的child
        cloneChildFibers(current, workInProgress);
        return workInProgress.child;
      }
    }
    
    • 回过头看 performUnitOfWork, return 了 child 之后,赋值给next
      let next;
      if (enableProfilerTimer) {
        // ... 跳过很多代码
        next = beginWork(current, workInProgress, nextRenderExpirationTime);
        // ... 跳过很多代码
      } else {
        next = beginWork(current, workInProgress, nextRenderExpirationTime);
        workInProgress.memoizedProps = workInProgress.pendingProps;
      }
      
      // ... 跳过很多代码
      
      if (next === null) {
        // If this doesn't spawn new work, complete the current work.
        next = completeUnitOfWork(workInProgress);
      }
      
      ReactCurrentOwner.current = null;
      // next 不为 null, 直接 return
      return next;
      
    • return next; 之后 回调 workLoop, 之后赋值给 nextUnitOfWork
      while (nextUnitOfWork !== null) {
          nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
        }
      
    • 通过 while 循环继续执行 child 的更新
    • 这就是循环能够成立的条件
  • 回到 beginWork 中当节点还有更新的时候,执行 switch case
    • 通过节点类型执行不同的方法来进行组件的更新
    • 比如 FunctionComponent, MemoComponent 等

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

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

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

相关文章

  • 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)
  • React源码解析18(5)------ 实现函数组件【修改beginWork和completeWork】

    经过之前的几篇文章,我们实现了基本的jsx,在页面渲染的过程。但是如果是通过函数组件写出来的组件,还是不能渲染到页面上的。 所以这一篇,主要是对之前写得方法进行修改,从而能够显示函数组件,所以现在我们在index.js文件中,修改一下jsx的写法。修改成函数组件

    2024年02月13日
    浏览(38)
  • React16源码: React中的setState和forceUpdate源码实现

    setState 和 forceUpdate 1 ) 概述 通过 class component 内部的 setState ,以及 forceUpdate 去更新一个组件的过程 在react的应用当中,我们只有 ReactDOM.render setState ,以及 forceUpdate 这几种种方式去更新react的应用是合理的,其他没有什么特别常用的方式去更新了 而且react官方推荐的也是用

    2024年01月25日
    浏览(40)
  • 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)
  • React16源码: React中的不同的expirationTime的源码实现

    不同的 expirationTime 1 )概述 在React中不仅仅有异步任务 大部分情况下都是同步的任务,所以会有不同 expirationTime 的存在 2 )种类 A. Sync 模式,优先级最高 任务创建完成之后,立马更新到真正的dom里面 是一个创建即更新的流程 B. Async 模式, 异步模式 会有一个调度 包含一系列

    2024年02月01日
    浏览(38)
  • React16源码: React中的update和updateQueue的源码实现

    React中的update和updateQueue 1 )概述 在 ReactDOM.render 过程中,还需要创建一个 update 对象 update 用于记录组件状态的改变的一个对象,它存放于Fiber对象的 updateQueue 中 updateQueue ,它是一个单向链表的结构,一次整体的更新过程当中 可能在这个queue里会存在多 Update 在这次更新的过

    2024年02月02日
    浏览(38)
  • React16源码: React中的PortalComponent创建, 调和, 更新的源码实现

    PortalComponent 1 )概述 React Portal之所以叫Portal,因为做的就是和“传送门”一样的事情 render到一个组件里面去,实际改变的是网页上另一处的DOM结构 主要关注 portal的创建, 调和, 更新过程 2 )源码 定位到 packages/react-dom/src/client/ReactDOM.js#L576 这里调用的是 ReactPortal.createPortal ,

    2024年01月21日
    浏览(42)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包