updateContainer
是 React 源码中负责更新容器的函数之一。初始化更新时调用就是该方法。这个函数位于 React 源码中的 ReactFiberReconciler.js
文件中,下面是对 updateContainer
函数的分析:
function updateContainer( element: ReactNodeList, container: OpaqueRoot, parentComponent: ?React$Component<any, any>, callback: ?Function, ): Lane { ... element是render方法的第一个入参,也就是入口模块(常见<App />写法) containers是createRoot方法的入参(最终挂载的容器),然后创建出来的FiberRoot, 在createRoot方法做了生成FiberRoot、委托事件到容器上。 最终返回一个赛道值 } // 类型 type ReactNodeList = | React$Element<any> | ReactPortal | ReactText | ReactFragment | ReactProvider<any> | ReactConsumer<any>; type OpaqueRoot = FiberRoot
updateContainer
函数接受四个参数:
element
:要在容器中呈现的 React 元素(render方法的第一个参数,是必填参数。也就是入口模块(常见<App />写法))container
:React 容器,通常是应用程序的根节点(必填入参。是createRoot方法的入参(最终挂载的容器),然后创建出来的FiberRoot)parentComponent
:可选参数,父组件,表示此容器的父组件。callback
:可选参数,更新容器后要执行的回调函数。
if (__DEV__) { // 开发阶段相关,不需要关心 onScheduleRoot(container, element); } // 这里根据不同的容器类型(如 DOM、React Native 等),获取相应的更新器 const current = container.current; // 计算当前时间(后面有讲方法内部的执行) const eventTime = requestEventTime(); // 计算当前更新的赛道 const lane = requestUpdateLane(current); if (enableSchedulingProfiler) { // 有关性能追踪的,不需要看 markRenderScheduled(lane); } // getContextForSubtree函数根据父组件计算子树的上下文(后面有例子说明) const context = getContextForSubtree(parentComponent); // 一旦应用或组件树中使用了Context API,并且已经对容器进行了首次渲染或上下文更新处理,container.context将不再为null。 if (container.context === null) { container.context = context; } else { container.pendingContext = context; } if (__DEV__) { // 开发阶段相关,不需要关心 if ( ReactCurrentFiberIsRendering && ReactCurrentFiberCurrent !== null && !didWarnAboutNestedUpdates ) { didWarnAboutNestedUpdates = true; // 检测是否在渲染过程中出现了嵌套更新,如果出现了嵌套更新,则给出警告。比如在render方法中更新state,又或者在componentDidUpdatef更新state,造成无限更新,陷入死循环 console.error( 'Render methods should be a pure function of props and state; ' + 'triggering nested component updates from render is not allowed. ' + 'If necessary, trigger nested updates in componentDidUpdate.\n\n' + 'Check the render method of %s.', getComponentNameFromFiber(ReactCurrentFiberCurrent) || 'Unknown', ); } } // 创建了一个更新对象 update,用于描述更新的内容,然后将要渲染的元素作为 payload 存入更新对象中,并将回调函数也存入更新对象中,例如,在初始化渲染,描述的更新内容就是将element渲染到页面 const update = createUpdate(eventTime, lane); // Caution: React DevTools currently depends on this property being called "element". // 译:React开发者工具依赖element属性 update.payload = {element}; callback = callback === undefined ? null : callback; if (callback !== null) { if (__DEV__) { // 开发阶段相关,不需要关心 if (typeof callback !== 'function') { console.error( 'render(...): Expected the last optional `callback` argument to be a ' + 'function. Instead received: %s.', callback, ); } } update.callback = callback; } // enqueueUpdate是负责将上面的更新对象update添加到当前的容器的更新队列上(后面会有讲解) const root = enqueueUpdate(current, update, lane); if (root !== null) { // 执行scheduleUpdateOnFiber,负责安排 Fiber 对象的更新 scheduleUpdateOnFiber(root, current, lane, eventTime); // 执行entangleTransitions,处理useTransition的,后面有安排讲解 entangleTransitions(root, current, lane); } // 最终返回一个 return lane;
eventTime:
记录每个更新的时间,在整个更新阶段,几乎都能看待eventTime的影子。它最终会被记录在更新对象中,是用来协调React的渲染和更新优先级的,确保可以按照正确的顺序和优先级执行。
requestEventTime:
调用performance.now()获取当前时间(毫秒级),并缓存,下次更新的时候如果有缓存,取缓存值。代表是同一批次,相同赛道等级的更新
var currentEventTime = -1 /// ..全局状态 /// ... function requestEventTime() { if ((executionContext & (RenderContext | CommitContext)) !== NoContext) { // 执行这里一般是初始化渲染,首次更新 // We're inside React, so it's fine to read the actual time. // 译:在React内部,读区当前时间 return now(); } // We're not inside React, so we may be in the middle of a browser event. // 译:如果当前不在React的控制流程中,可能处于浏览器的事件处理中 if (currentEventTime !== NoTimestamp) { // 执行这里一般是多次同步执行更新状态,例如多次同步执行setState // Use the same start time for all updates until we enter React again. // 译:直到开始一次新的更新前,所有更新使用相同的时间。 return currentEventTime; } // This is the first update since React yielded. Compute a new start time currentEventTime = now(); /// now = performance.now return currentEventTime; }
这里说明下要注意的几点:
executionContext:
表示render阶段的执行上下文CommitContext:
表示commit阶段执行上下文currentEventTime:
是一个全局状态,默认值-1
这里缓存currentEventTime,
一个重要的原因是在一个更新周期内,很多状态更新可能几乎同时发生(例如多次同步调用setState)。如果每个更新都记录一个独立的时间戳,这会导致同一周期内的不同更新事件有时间差异,从而可能引发问题,尤其是在并发处理中,协调React的渲染和更新优先级,确保正确的顺序和优先级执行都是通过requestEventTime计算保证的。
Lane
:更新赛道应该在React中很重要的一个概念来,具体看Lane章节
这里只讲方法相关内容
requestUpdateLane
:计算更新赛道
function requestUpdateLane(fiber) { var mode = fiber.mode; if ((mode & ConcurrentMode) === NoMode) { // 如果不是并发模式,则同步更新,如果是18版本,不会到这里 return SyncLane; } else if ( (executionContext & RenderContext) !== NoContext && workInProgressRootRenderLanes !== NoLanes) { // 可以不用看这里,官方不支持 // 更新发生在render阶段,并且已经有正在进行的赛道,,则以当前渲染的赛道计算出最高等级赛道并返回 // This is a render phase update. These are not officially supported. The // old behavior is to give this the same "thread" (lanes) as // whatever is currently rendering. So if you call `setState` on a component // that happens later in the same render, it will flush. Ideally, we want to // remove the special case and treat them as if they came from an // interleaved event. Regardless, this pattern is not officially supported. // This behavior is only a fallback. The flag only exists until we can roll // out the setState warning, since existing code might accidentally rely on // the current behavior. // 译:这是发生在render阶段的更新。官方不支持这种行为。 // 以前做法是将正在渲染内容的 "线程"(赛道)赋予这个更新。 // 因此,如果在同一渲染阶段,如果稍晚一点调用 `setState` ,他会刷新。 // 理想情况下,我们想移除这种情况和修复他们,并将他们视为是交互事件。 // 但无论如何,官方都不会支持这种做法。这种做法仅作为备用的。该标志只存在到我们可以发出 setState 警告为止、因此可能有代码会无意触发了这这里的动作 // 根据 workInProgressRootRenderLanes 计算出最高等级的赛道 return pickArbitraryLane(workInProgressRootRenderLanes); } var isTransition = requestCurrentTransition() !== NoTransition; if (isTransition) { // 如果没用startTransition,不用关心这里 if ( ReactCurrentBatchConfig$3.transition !== null) { var transition = ReactCurrentBatchConfig$3.transition; if (!transition._updatedFibers) { transition._updatedFibers = new Set(); } transition._updatedFibers.add(fiber); } // The algorithm for assigning an update to a lane should be stable for all // updates at the same priority within the same event. To do this, the // inputs to the algorithm must be the same. // // The trick we use is to cache the first of each of these inputs within an // event. Then reset the cached values once we can be sure the event is // over. Our heuristic for that is whenever we enter a concurrent work loop. if (currentEventTransitionLane === NoLane) { // All transitions within the same event are assigned the same lane. currentEventTransitionLane = claimNextTransitionLane(); } return currentEventTransitionLane; } // Updates originating inside certain React methods, like flushSync, have // their priority set by tracking it with a context variable. // The opaque type returned by the host config is internally a lane, so we can // use that directly. // TODO: Move this type conversion to the event priority module. // 译:源自React内部的更新,像flushSync(同步刷新)的优先级是通过用一个环境变量跟踪其设定的. // 返回的未知类型是来自内部的赛道,可以直接使用 // 获取更新赛道(updateLane不为空,代表同一批次的更新) var updateLane = getCurrentUpdatePriority(); if (updateLane !== NoLane) { return updateLane; } // This update originated outside React. Ask the host environment for an // appropriate priority, based on the type of event. // // The opaque type returned by the host config is internally a lane, so we can // use that directly. // TODO: Move this type conversion to the event priority module. // 译:源自React外部的更新,要求基于事件类型得到一个合适的优先级 // // 返回的未知类型是内部的赛道,可以直接使用 var eventLane = getCurrentEventPriority(); return eventLane; }
这里还是比较好理解,判断当前环境是否是并发模式,不是则同步更新,然后判断当前是否处于渲染阶段(render阶段),并且是否已经有正进行的更新Fiber,然后返回正在渲染的Fiber最高级赛道,否之,如果有用到过度方案,如useStranisition,就返回该方案对应的赛道,又或者是否使用了内部的方法作为更新方案,那返回对应的赛道,如果上述都不满足,根据当前更新的事件类型返回对应的赛道。
getContextForSubtree
获取子组件树最近的Context。初始化渲染返回的是一个空对象。在React18版本,getContextForSubtree主要服务于一个unstable_renderSubtreeIntoContainer方法,该方法官方已经声明废弃。不过可以跟着unstable_renderSubtreeIntoContainer方法往下学习下getContextForSubtree是如何获取最近的Context的
function getContextForSubtree(parentComponent) { if (!parentComponent) { // 如果夫组件不存在,返回一个空对象 return emptyContextObject; } var fiber = get(parentComponent); // 获取父组件的Fiber // 找到最近的Context, var parentContext = findCurrentUnmaskedContext(fiber); if (fiber.tag === ClassComponent) { var Component = fiber.type; if (isContextProvider(Component)) { return processChildContext(fiber, Component, parentContext); } } return parentContext; } // findCurrentUnmaskedContext 方法就是从当前节点向上查找到最近的Context并返回 function findCurrentUnmaskedContext(fiber) { { if (!isFiberMounted(fiber) || fiber.tag !== ClassComponent) { throw new Error('...'); } var node = fiber; do { switch (node.tag) { case HostRoot: return node.stateNode.context; case ClassComponent: { var Component = node.type; if (isContextProvider(Component)) { return node.stateNode.__reactInternalMemoizedMergedChildContext; } break; } } node = node.return; } while (node !== null); throw new Error('...'); } } /// unstable_renderSubtreeIntoContainer方法最终调的是legacyRenderSubtreeIntoContainer /// 这里只讨论parentComponent, children, container,分别是夫组件,要渲染的组件和容器 /// 到这里可以知道,把夫组件传递进来主要是为了子组件可以从夫组件拿到Context function legacyRenderSubtreeIntoContainer(parentComponent, children, container, forceHydrate, callback) { var maybeRoot = container._reactRootContainer; var root; if (!maybeRoot) { // Initial mount root = legacyCreateRootFromDOMContainer(container, children, parentComponent, callback, forceHydrate); } else { root = maybeRoot; if (typeof callback === 'function') { var originalCallback = callback; callback = function () { var instance = getPublicRootInstance(root); originalCallback.call(instance); }; } // Update updateContainer(children, root, parentComponent, callback); // } return getPublicRootInstance(root); }
createUpdate
没什么好讲的,就是缓存了eventTime和Lane,然后返回一个对象
enqueueUpdate
用于将更新入队到Fiber节点的函数,是非常重要的方法。
enqueueUpdate<State>( fiber: Fiber, update: Update<State>, lane: Lane, ): FiberRoot | null { const updateQueue = fiber.updateQueue; if (updateQueue === null) { // Only occurs if the fiber has been unmounted. // 译:卸载了为null return null; } // Fiber上的共享更新队列,存储所有即将处理的更新 const sharedQueue: SharedQueue<State> = (updateQueue: any).shared; if (__DEV__) { // 开发阶段的警报,可以不要关心 // 为了防止更新函数的二次更新,如在setSate的第二个函数入参中再次更新状态,这是官方不推荐的 if ( currentlyProcessingQueue === sharedQueue && !didWarnUpdateInsideUpdate ) { console.error( 'An update (setState, replaceState, or forceUpdate) was scheduled ' + 'from inside an update function. Update functions should be pure, ' + 'with zero side-effects. Consider using componentDidUpdate or a ' + 'callback.', ); didWarnUpdateInsideUpdate = true; } } if (isUnsafeClassRenderPhaseUpdate(fiber)) { // 如果是在render阶段又重新更新状态,会进入到这里来 // 这里isUnsafeClassRenderPhaseUpdate比较简单,就一行代码,判断是否处于render阶段上下文 // 通常这种不安全的更新指在UNSAFE_componentWillMount或者在render钩子函数更新状态,又或者凡事在render阶段前执行的钩子函数中更新了状态,这类都属于不安全更新 // This is an unsafe render phase update. Add directly to the update // queue so we can process it immediately during the current render. // 译:这是不安全渲染阶段的更新。在渲染期间,直接添加到更新队列,然后立即执行 const pending = sharedQueue.pending; if (pending === null) { // This is the first update. Create a circular list. update.next = update; } else { update.next = pending.next; pending.next = update; } // 最终update会是一个链环数据结构 sharedQueue.pending = update; // Update the childLanes even though we're most likely already rendering // this fiber. This is for backwards compatibility in the case where you // update a different component during render phase than the one that is // currently renderings (a pattern that is accompanied by a warning). return unsafe_markUpdateLaneFromFiberToRoot(fiber, lane); } else { return enqueueConcurrentClassUpdate(fiber, sharedQueue, update, lane); } } var unsafe_markUpdateLaneFromFiberToRoot = markUpdateLaneFromFiberToRoot; function enqueueConcurrentClassUpdate(fiber, queue, update, lane) { var interleaved = queue.interleaved; // 同在上面的不安全更新一样,同样把更新的链成一个链环的数据结构(图2所示) if (interleaved === null) { // This is the first update. Create a circular list. update.next = update; // At the end of the current render, this queue's interleaved updates will // be transferred to the pending queue. // 这里只需要知道把queue存到一个全局的队列中即可 pushConcurrentUpdateQueue(queue); } else { update.next = interleaved.next; interleaved.next = update; } queue.interleaved = update; return markUpdateLaneFromFiberToRoot(fiber, lane); } // 可以看到,enqueueConcurrentClassUpdate最后也是返回了markUpdateLaneFromFiberToRoot的结果 // markUpdateLaneFromFiberToRoot就比较好理解,就是从当前fiber节点开始,合并优先级,直到顶层节点,然后返回容器节点 function markUpdateLaneFromFiberToRoot(sourceFiber, lane) { // Update the source fiber's lanes sourceFiber.lanes = mergeLanes(sourceFiber.lanes, lane); var alternate = sourceFiber.alternate; if (alternate !== null) { alternate.lanes = mergeLanes(alternate.lanes, lane); } { if (alternate === null && (sourceFiber.flags & (Placement | Hydrating)) !== NoFlags) { warnAboutUpdateOnNotYetMountedFiberInDEV(sourceFiber); } } // Walk the parent path to the root and update the child lanes. var node = sourceFiber; var parent = sourceFiber.return; while (parent !== null) { parent.childLanes = mergeLanes(parent.childLanes, lane); alternate = parent.alternate; if (alternate !== null) { alternate.childLanes = mergeLanes(alternate.childLanes, lane); } else { { if ((parent.flags & (Placement | Hydrating)) !== NoFlags) { warnAboutUpdateOnNotYetMountedFiberInDEV(sourceFiber); } } } node = parent; parent = parent.return; } if (node.tag === HostRoot) { var root = node.stateNode; return root; } else { return null; } }
到这里算是updateContainer方法及其相关的方法都已经结束,后面就是开始执行scheduleUpdateOnFiber
和entangleTransitions
,进入下一个阶段