updateContainer
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,进入下一个阶段
浙公网安备 33010602011771号