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的资料请关注我们其它相关文章!