React Fiber构建beginWork源码解析

目录
  • 引言
  • 一. scheduleUpdateOnFiber
  • 二. performSyncWorkOnRoot
    • renderRootSync
    • workLoopSync
    • performUnitOfWork
  • 三. beginWork
    • updateHostRoot
    • reconcileChildren
    • reconcileChildFibers
    • reconcileSingleElement
    • createFiberFromElement
    • mountIndeterminateComponent
    • renderWithHooks

引言

前文我们介绍了fiber的基本概念,以及fiber在初始化阶段生成了fiberRoot和rootFiber 2个对象。

但,整个fiber树还没有构建,未进入reconciler阶段。

本篇,我们将介绍,fiber链表的构建之-beginWork阶段

一. scheduleUpdateOnFiber

function scheduleUpdateOnFiber(fiber, lane, eventTime) {
  // ...
  if (root === workInProgressRoot) {
      {
        workInProgressRootUpdatedLanes = mergeLanes(workInProgressRootUpdatedLanes, lane);
      }
    }
  // ...
  if (lane === SyncLane) {
      if ( // Check if we're inside unbatchedUpdates
      (executionContext & LegacyUnbatchedContext) !== NoContext && // Check if we're not already rendering
      (executionContext & (RenderContext | CommitContext)) === NoContext) {
        // ...
        performSyncWorkOnRoot(root)
      }else {
        // ...
        ensureRootIsScheduled(root, eventTime);
      }
  }else {
    // ...
     ensureRootIsScheduled(root, eventTime);
     schedulePendingInteractions(root, lane);
  }
}

fiber在内存中,会有两份数据,一个是当前的,一个是在内存中正在构建的。

这里 根据不同的启动模式,进行下面的协调阶段。在17版本中,一般使用sync模式。18版本默认开启并发模式。

二. performSyncWorkOnRoot

同步模式下的流程,如果是并发模式,会进入schedule异步调度,最终还会执行performSyncWorkOnRoot。

function performSyncWorkOnRoot(root) {
    flushPassiveEffects();
    var lanes;
    var exitStatus;
    if (root === workInProgressRoot && includesSomeLane(root.expiredLanes, workInProgressRootRenderLanes)) {
      lanes = workInProgressRootRenderLanes;
      exitStatus = renderRootSync(root, lanes);
      if (includesSomeLane(workInProgressRootIncludedLanes, workInProgressRootUpdatedLanes)) {
        lanes = getNextLanes(root, lanes);
        exitStatus = renderRootSync(root, lanes);
      }
    } else {
      lanes = getNextLanes(root, NoLanes);
      exitStatus = renderRootSync(root, lanes);
    }
    // ...
    var finishedWork = root.current.alternate;
    root.finishedWork = finishedWork;
    root.finishedLanes = lanes;
    commitRoot(root);
    ensureRootIsScheduled(root, now());
    return null;
}

performSyncWorkOnRoot是reconciler阶段所有的执行入口,首次渲染将进入renderRootSync。

问题来了,为什么要先执行flushPassiveEffects?这里留个悬念,在后续的更新流程中我们再提及。

关于nextLanes,这里我先抛开,先理解为render优先级,lane模型会在后续章节系统性的讲解。

renderRootSync

function renderRootSync(root, lanes) {
  if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
      prepareFreshStack(root, lanes);
      startWorkOnPendingInteractions(root, lanes);
    }
   // ...
    do {
      try {
        workLoopSync();
        break;
      } catch (thrownValue) {
        handleError(root, thrownValue);
      }
    } while (true);
    // ...
}

prepare阶段,可以构建双缓存fiber,即workInProgressRoot,内存中的fiber通过之前的createFiber方法调用,当前fiber和内存中fiber通过alternate相互引用。

workLoopSync即react两大工作循环中的第一层循环,从这里开始构建fiber链表。

workLoopSync

function workLoopSync() {
    while (workInProgress !== null) {
      performUnitOfWork(workInProgress);
    }
  }

这里是同步构建的情况,值得对比的是另外一个方法:

function workLoopConcurrent() {
    while (workInProgress !== null && !shouldYield()) {
      performUnitOfWork(workInProgress);
    }
  }

此方法是并发模式下的工作模式,两者区别在于shouldYield。shouldYield由schedule调度器控制,react自己实现了一套浏览器空闲时的任务调度。 其实,浏览器本身有对应的api:requestIdCallback。但不同浏览器执行有时间差异,不能满足react设计需要。

performUnitOfWork

function performUnitOfWork(unitOfWork) {
    var current = unitOfWork.alternate;
    // ...
    var next;
    if ( (unitOfWork.mode & ProfileMode) !== NoMode) {
      startProfilerTimer(unitOfWork);
      next = beginWork$1(current, unitOfWork, subtreeRenderLanes);
      stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
    } else {
      next = beginWork$1(current, unitOfWork, subtreeRenderLanes);
    }
    resetCurrentFiber();
    unitOfWork.memoizedProps = unitOfWork.pendingProps;
    if (next === null) {
      completeUnitOfWork(unitOfWork);
    } else {
      workInProgress = next;
    }
    ReactCurrentOwner$2.current = null;
  }

profile是react内部性能跟踪调试器,在正常的开发生产环境不会主动开启,将进入beginWork阶段

三. beginWork

  function beginWork(current, workInProgress, renderLanes) {
      if (current !== null) {
      var oldProps = current.memoizedProps;
      var newProps = workInProgress.pendingProps;
      if (oldProps !== newProps || hasContextChanged() || ( // Force a re-render if the implementation changed due to hot reload:
       workInProgress.type !== current.type )) {
        didReceiveUpdate = true;
      } else if (!includesSomeLane(renderLanes, updateLanes)) {
        didReceiveUpdate = false;
        switch (workInProgress.tag) {
          // ...
        }
      } else {
        if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
          didReceiveUpdate = true;
        } else {
          didReceiveUpdate = false;
        }
      }
    } else {
      didReceiveUpdate = false;
    }
    workInProgress.lanes = NoLanes;
    switch (workInProgress.tag) {
      case IndeterminateComponent:
        {
          return mountIndeterminateComponent(current, workInProgress, workInProgress.type, renderLanes);
        }
      case LazyComponent:
        {
          var elementType = workInProgress.elementType;
          return mountLazyComponent(current, workInProgress, elementType, updateLanes, renderLanes);
        }
      case FunctionComponent:
        {
          var _Component = workInProgress.type;
          var unresolvedProps = workInProgress.pendingProps;
          var resolvedProps = workInProgress.elementType === _Component ? unresolvedProps : resolveDefaultProps(_Component, unresolvedProps);
          return updateFunctionComponent(current, workInProgress, _Component, resolvedProps, renderLanes);
        }
      case ClassComponent:
        {
          var _Component2 = workInProgress.type;
          var _unresolvedProps = workInProgress.pendingProps;
          var _resolvedProps = workInProgress.elementType === _Component2 ? _unresolvedProps : resolveDefaultProps(_Component2, _unresolvedProps);
          return updateClassComponent(current, workInProgress, _Component2, _resolvedProps, renderLanes);
        }
      case HostRoot:
        return updateHostRoot(current, workInProgress, renderLanes);
      case HostComponent:
        return updateHostComponent(current, workInProgress, renderLanes);
      case HostText:
        return updateHostText(current, workInProgress);
      case SuspenseComponent:
        return updateSuspenseComponent(current, workInProgress, renderLanes);
      case HostPortal:
        return updatePortalComponent(current, workInProgress, renderLanes);
      case ForwardRef:
        {
          var type = workInProgress.type;
          var _unresolvedProps2 = workInProgress.pendingProps;
          var _resolvedProps2 = workInProgress.elementType === type ? _unresolvedProps2 : resolveDefaultProps(type, _unresolvedProps2);
          return updateForwardRef(current, workInProgress, type, _resolvedProps2, renderLanes);
        }
      case Fragment:
        return updateFragment(current, workInProgress, renderLanes);
      case Mode:
        return updateMode(current, workInProgress, renderLanes);
      case Profiler:
        return updateProfiler(current, workInProgress, renderLanes);
      case ContextProvider:
        return updateContextProvider(current, workInProgress, renderLanes);
      case ContextConsumer:
        return updateContextConsumer(current, workInProgress, renderLanes);
      case MemoComponent:
        {
          var _type2 = workInProgress.type;
          var _unresolvedProps3 = workInProgress.pendingProps; // Resolve outer props first, then resolve inner props.
          var _resolvedProps3 = resolveDefaultProps(_type2, _unresolvedProps3);
          {
            if (workInProgress.type !== workInProgress.elementType) {
              var outerPropTypes = _type2.propTypes;
              if (outerPropTypes) {
                checkPropTypes(outerPropTypes, _resolvedProps3, // Resolved for outer only
                'prop', getComponentName(_type2));
              }
            }
          }
          _resolvedProps3 = resolveDefaultProps(_type2.type, _resolvedProps3);
          return updateMemoComponent(current, workInProgress, _type2, _resolvedProps3, updateLanes, renderLanes);
        }
      case SimpleMemoComponent:
        {
          return updateSimpleMemoComponent(current, workInProgress, workInProgress.type, workInProgress.pendingProps, updateLanes, renderLanes);
        }
      case IncompleteClassComponent:
        {
          var _Component3 = workInProgress.type;
          var _unresolvedProps4 = workInProgress.pendingProps;
          var _resolvedProps4 = workInProgress.elementType === _Component3 ? _unresolvedProps4 : resolveDefaultProps(_Component3, _unresolvedProps4);
          return mountIncompleteClassComponent(current, workInProgress, _Component3, _resolvedProps4, renderLanes);
        }
      case SuspenseListComponent:
        {
          return updateSuspenseListComponent(current, workInProgress, renderLanes);
        }
      case FundamentalComponent:
        {
          break;
        }
      case ScopeComponent:
        {
          break;
        }
      case Block:
        {
          {
            var block = workInProgress.type;
            var props = workInProgress.pendingProps;
            return updateBlock(current, workInProgress, block, props, renderLanes);
          }
        }
      case OffscreenComponent:
        {
          return updateOffscreenComponent(current, workInProgress, renderLanes);
        }
      case LegacyHiddenComponent:
        {
          return updateLegacyHiddenComponent(current, workInProgress, renderLanes);
        }
    }
  }

其中 didReceiveUpdate会在更新ref时使用到,也是props是否有变化的标志,这里先不关注。

首次将进入rootFiber的case,即HostRoot

updateHostRoot

function updateHostRoot(current, workInProgress, renderLanes) {
  // ...
  // 服务器端渲染处理先省略...
  // ...
  reconcileChildren(current, workInProgress, nextChildren, renderLanes);
  return workInProgress.child;
}

reconcileChildren

function reconcileChildren(current, workInProgress, nextChildren, renderLanes) {
    if (current === null) {
      // If this is a fresh new component that hasn't been rendered yet, we
      // won't update its child set by applying minimal side-effects. Instead,
      // we will add them all to the child before it gets rendered. That means
      // we can optimize this reconciliation pass by not tracking side-effects.
      workInProgress.child = mountChildFibers(workInProgress, null, nextChildren, renderLanes);
    } else {
      // If the current child is the same as the work in progress, it means that
      // we haven't yet started any work on these children. Therefore, we use
      // the clone algorithm to create a copy of all the current children.
      // If we had any progressed work already, that is invalid at this point so
      // let's throw it out.
      workInProgress.child = reconcileChildFibers(workInProgress, current.child, nextChildren, renderLanes);
    }
  }

对于首次渲染,current为Null,进入reconcileChildFibers

reconcileChildFibers

function reconcileChildFibers(returnFiber, currentFirstChild, newChild, lanes) {
  var isObject = typeof newChild === 'object' && newChild !== null;
      if (isObject) {
        switch (newChild.$$typeof) {
          case REACT_ELEMENT_TYPE:
            return placeSingleChild(reconcileSingleElement(returnFiber, currentFirstChild, newChild, lanes));
          case REACT_PORTAL_TYPE:
            return placeSingleChild(reconcileSinglePortal(returnFiber, currentFirstChild, newChild, lanes));
          case REACT_LAZY_TYPE:
            {
              var payload = newChild._payload;
              var init = newChild._init; // TODO: This function is supposed to be non-recursive.
              return reconcileChildFibers(returnFiber, currentFirstChild, init(payload), lanes);
            }
        }
      }
}

这里newChild是element对象,即render初始化阶段所生成的react element。

这里,我们可以看到typeof了,除了是个symbol,防止伪造攻击外,对于不同的类型,会有不同的fiber构建流程。普通的typeof了,除了是个symbol,防止伪造攻击外,对于不同的类型,会有不同的fiber构建流程。普通的typeof了,除了是个symbol,防止伪造攻击外,对于不同的类型,会有不同的fiber构建流程。普通的typeof是element type。

reconcileSingleElement

function reconcileSingleElement(returnFiber, currentFirstChild, element, lanes) {
      var key = element.key;
      var child = currentFirstChild;
      // ... 如果存在child,递归删除
      // ...
      if (element.type === REACT_FRAGMENT_TYPE) {
        var created = createFiberFromFragment(element.props.children, returnFiber.mode, lanes, element.key);
        created.return = returnFiber;
        return created;
      } else {
        var _created4 = createFiberFromElement(element, returnFiber.mode, lanes);
        _created4.ref = coerceRef(returnFiber, currentFirstChild, element);
        _created4.return = returnFiber;
        return _created4;
      }
}

createFiberFromElement

function createFiberFromElement(element, mode, lanes) {
    var owner = null;
    {
      owner = element._owner;
    }
    var type = element.type;
    var key = element.key;
    var pendingProps = element.props;
    var fiber = createFiberFromTypeAndProps(type, key, pendingProps, owner, mode, lanes);
    {
      fiber._debugSource = element._source;
      fiber._debugOwner = element._owner;
    }
    return fiber;
  }

根据element对象创建子节点fiber树,并设置构建的子fiber.return为父fiber。遍历的方式使用的是深度优先遍历算法,一边对子节点做fiber实例化,一边对节点的上下关系做绑定

mountIndeterminateComponent

function mountIndeterminateComponent(_current, workInProgress, Component, renderLanes) {
  // ...
  prepareToReadContext(workInProgress, renderLanes);
  // ...
  setIsRendering(true);
  ReactCurrentOwner$1.current = workInProgress;
  value = renderWithHooks(null, workInProgress, Component, props, context, renderLanes);
  setIsRendering(false);
  // ...
  reconcileChildren(null, workInProgress, value, renderLanes);
  // ...
}

自定义组件,将先设置rendering状态以及全局的render fiber进行时对象,自定义组件内可能有副作用,比如useEffect,会影响flags。

renderWithHooks

function renderWithHooks(current, workInProgress, Component, props, secondArg, nextRenderLanes) {
  // ...
  {
      if (current !== null && current.memoizedState !== null) {
        ReactCurrentDispatcher$1.current = HooksDispatcherOnUpdateInDEV;
      } else if (hookTypesDev !== null) {
        ReactCurrentDispatcher$1.current = HooksDispatcherOnMountWithHookTypesInDEV;
      } else {
        ReactCurrentDispatcher$1.current = HooksDispatcherOnMountInDEV;
      }
    }
    var children = Component(props, secondArg);
    ReactCurrentDispatcher$1.current = ContextOnlyDispatcher;
}

ReactCurrentDispatcher对象很重要,这是effect处理重要的全局对象,他将改变flags值,并影响后续的effect链表构建。

Component即函数组件对象,执行的结果即element对象。此element对象将再次调用reconcileChildren,进入协调child阶段,最终返回child的Fiber。

问题来了:

  • fiber是一边生成,一边关联关系的,那么每层的fiber是如何找到下层element的?
  • 不同的fiber对象,updateQueue都一样吗?
  • 不同的fiber对象,memoizedState都一样吗?

对于rootFiber而言,updateQueue挂载的element对象,经过process update,清空updateQueue.shared,进而将element对象挂载至memoizedState上,当执行reconcileChildren时,nextChild从memoizedState获取。

对于nextChild为function组件时,包括顶层函数组件,将执行renderWithHooks,返回全量的element对象,当然renderWithHooks功能不仅仅于此,还涉及重要的flags计算。当执行reconcileChildren时,会将element挂载至下层fiber的pendingProps上。

对于nextChild为普通节点时,会根据层层根据pendingProps获取下一层节点的信息,从而继续构建fiber树。

值得注意的是,函数组件节点的updateQueue指的是lastEffect链表,他其实是一个环状链表结构。

每个节点的构建,都会设置memoizedProps = pendingProps

至此,beginWork的递归构建已完成,下面将进入completeWork,更多关于React Fiber构建beginWork的资料请关注我们其它相关文章!

(0)

相关推荐

  • React超详细讲述Fiber的使用

    目录 Fiber 概念 结构 Fiber树的遍历是这样发生的深度遍历 window.requestIdleCallback() requestAnimationFrame Fiber是如何工作的 结论 Fiber 概念 JavaScript引擎和页面渲染引擎两个线程是互斥的,当其中一个线程执行时,另一个线程只能挂起等待 如果 JavaScript 线程长时间地占用了主线程,那么渲染层面的更新就不得不长时间地等待,界面长时间不更新,会导致页面响应度变差,用户可能会感觉到卡顿 破解JavaScrip

  • React Fiber构建completeWork源码解析

    目录 引言 一. completeUnitOfWork 二. completeWork createInstance createElement appendAllChildren 三. Effect useEffect resolveDispatcher mountEffectImpl pushEffect 四. rootFiber-Effect 引言 之前我们介绍了beginWork,react使用的是深度优先遍历算法,整个fiber的构建都遵循此算法. 这也意味着,并不是所有节点begin

  • React Fiber树的构建和替换过程讲解

    目录 前言 mount 过程 update 过程 前言 React Fiber树的创建和替换过程运用了双缓存技术,即先在内存中创建 fiber 树,待 fiber 树创建完成以后,直接将旧的 fiber 树替换成新的 fiber 树,这样做的好处是省去了直接在页面上渲染时的计算时间,避免计算量大导致的白屏.卡顿,现在你一定还不太理解,下面进行详细讲解! mount 过程 以一下demo为例进行讲解: function App() { const [num, add] = useState(0);

  • React Fiber与调和深入分析

    目录 一 引沿 二 什么是调和 三 什么是Filber 四 实现调和的过程 1. 创建FiberRoot 2. render阶段 五 总结 一 引沿 Fiber 架构是React16中引入的新概念,目的就是解决大型 React 应用卡顿,React在遍历更新每一个节点的时候都不是用的真实DOM,都是采用虚拟DOM,所以可以理解成fiber就是React的虚拟DOM,更新Fiber的过程叫做调和,每一个fiber都可以作为一个执行单元来处理,所以每一个 fiber 可以根据自身的过期时间expir

  • React Fiber原理深入分析

    目录 为什么需要 fiber fiber 之前 fiber 之后 fiber 节点结构 dom 相关属性 tag key 和 type stateNode 链表树相关属性 副作用相关属性 flags Effect List 其他 lane alternate fiber 树的构建与更新 mount 过程 update 过程 总结 react16 版本之后引入了 fiber,整个架构层面的 调度.协调.diff 算法以及渲染等都与 fiber 密切相关.所以为了更好地讲解后面的内容,需要对 fib

  • React Fiber 树思想解决业务实际场景详解

    目录 熟悉 Fiber 树结构 业务场景 熟悉 Fiber 树结构 我们知道,React 从 V16 版本开始采用 Fiber 树架构来实现渲染和更新机制. Fiber 在 React 源码中可以看作是一个任务执行单元,每个 React Element 都会有一个与之对应的 Fiber 节点. Fiber 节点的核心数据结构如下: type Fiber = { type: any, //类型 return: Fiber, //父节点 child: Fiber, // 指向第一个子节点 sibli

  • React Fiber构建beginWork源码解析

    目录 引言 一. scheduleUpdateOnFiber 二. performSyncWorkOnRoot renderRootSync workLoopSync performUnitOfWork 三. beginWork updateHostRoot reconcileChildren reconcileChildFibers reconcileSingleElement createFiberFromElement mountIndeterminateComponent renderW

  • react diff算法源码解析

    React中Diff算法又称为调和算法,对应函数名为reconcileChildren,它的主要作用是标记更新过程中那些元素发生了变化,这些变化包括新增.移动.删除.过程发生在beginWork阶段,只有非初次渲染才会Diff. 以前看过一些文章将Diff算法表述为两颗Fiber树的比较,这是不正确的,实际的Diff过程是一组现有的Fiber节点和新的由JSX生成的ReactElement的比较,然后生成新的Fiber节点的过程,这个过程中也会尝试复用现有Fiber节点. 节点Diff又分为两种

  • React事件机制源码解析

    React v17里事件机制有了比较大的改动,想来和v16差别还是比较大的. 本文浅析的React版本为17.0.1,使用ReactDOM.render创建应用,不含优先级相关. 原理简述 React中事件分为委托事件(DelegatedEvent)和不需要委托事件(NonDelegatedEvent),委托事件在fiberRoot创建的时候,就会在root节点的DOM元素上绑定几乎所有事件的处理函数,而不需要委托事件只会将处理函数绑定在DOM元素本身. 同时,React将事件分为3种类型--d

  • React实时预览react-live源码解析

    目录 引言 源码解读 输入内容 Provider generateElement 其他组件 总结 引言 react-live 是一个 react 的实时编辑器,可直接编辑 react 代码,并实时预览.可以看下官方的预览图: 本文针对的源码版本 src ├── components │ ├── Editor │ │ └── index.js │ └── Live │ ├── LiveContext.js │ ├── LiveEditor.js │ ├── LiveError.js │ ├── L

  • 微前端架构ModuleFederationPlugin源码解析

    目录 序言 背景 MF 基本介绍 应用场景 微前端架构 服务化的 library 和 components ModuleFederationPlugin 源码解析 入口源码 Exposes Remotes Shared 小结 总结 序言 本文是 Webpack ModuleFederationPlugin(后面简称 MF) 源码解析 文章中的第一篇,在此系列文章中,我将带领大家抽丝剥茧.一步步地去解析 MF 源码.当然为了帮助大家理解,可能中间也会涉及到 Webpack 源码中的其它实现,我会根

  • Redis源码解析:集群手动故障转移、从节点迁移详解

    一:手动故障转移 Redis集群支持手动故障转移.也就是向从节点发送"CLUSTER  FAILOVER"命令,使其在主节点未下线的情况下,发起故障转移流程,升级为新的主节点,而原来的主节点降级为从节点. 为了不丢失数据,向从节点发送"CLUSTER  FAILOVER"命令后,流程如下: a:从节点收到命令后,向主节点发送CLUSTERMSG_TYPE_MFSTART包:          b:主节点收到该包后,会将其所有客户端置于阻塞状态,也就是在10s的时间内

  • java TreeMap源码解析详解

    java TreeMap源码解析详解 在介绍TreeMap之前,我们来了解一种数据结构:排序二叉树.相信学过数据结构的同学知道,这种结构的数据存储形式在查找的时候效率非常高. 如图所示,这种数据结构是以二叉树为基础的,所有的左孩子的value值都是小于根结点的value值的,所有右孩子的value值都是大于根结点的.这样做的好处在于:如果需要按照键值查找数据元素,只要比较当前结点的value值即可(小于当前结点value值的,往左走,否则往右走),这种方式,每次可以减少一半的操作,所以效率比较高

  • .Net Core中ObjectPool的使用与源码解析

    一.对象池 运用对象池化技术可以显著地提升性能,尤其是当对象的初始化过程代价较大或者频率较高.下面是ObjectPool源码中涉及的几个类.当你看过.Net Core源码很多时,你会发现,微软的开发很多都是这种模式,通过Policy构建Provider,通过Provider创建最终的类. 二.使用 这个组件的目的主要是将对象保存到对象池,用的时候直接去取,不需要重新创建,实现对象的重复利用.但是有个问题,假如对象池中开始没有对象或者取得数量大于对象池中的数量怎么办?在对象池中对象的数量不足时,此

  • .NET Core源码解析配置文件及依赖注入

    写在前面 上篇文章我给大家讲解了ASP.NET Core的概念及为什么使用它,接着带着你一步一步的配置了.NET Core的开发环境并创建了一个ASP.NET Core的mvc项目,同时又通过一个实战教你如何在页面显示一个Content的列表.不知道你有没有跟着敲下代码,千万不要做眼高手低的人哦. 这篇文章我们就会设计一些复杂的概念了,因为要对ASP.NET Core的启动及运行原理.配置文件的加载过程进行分析,依赖注入,控制反转等概念的讲解等. 俗话说,授人以鱼不如授人以渔,所以文章旨在带着大

随机推荐