React源码3:update、fiber.updateQueue对象数据结构和updateContainer()中enqueueUpdate()阶段
回顾:初始化创建完FiberRootNode和hostRootFiber后,调用render方法进行更新root。
updateContainer()主流程
- updateContainer(入口)
- 获取 current Fiber(HostRoot)
- 获取事件时间(eventTime)、优先级(eventLane)
- 处理 context(大部分情况为默认)
- 创建 update 对象(payload: element)
- 入队并调度(enqueueUpdate,使用创建的update和初始化获得的fiber.updateQueue)
- 返回 lane(实际情况返回值无变量接收,基本不使用返回值)
数据结构1:update对象属性及其属性值来源
update对象封装在createUpdate方法中,工厂函数返回一个创建的update对象。
有6个属性。
//全局变量
var UpdateState = 0;
//对update的属性赋值
var eventTime = requestEventTime();
var lane = requestUpdateLane(current$1);
var update = createUpdate(eventTime, lane); // Caution: React DevTools currently depends on this property
// being called "element".//工厂函数,返回一个update对象
function createUpdate(eventTime, lane) {var update = {eventTime: eventTime,lane: lane,tag: UpdateState,payload: null,callback: null,next: null,};return update;
}
update.payload = {element: element,
};callback = callback === undefined ? null : callback;if (callback !== null) {{if (typeof callback !== 'function') {error('render(...): Expected the last optional `callback` argument to be a ' +'function. Instead received: %s.',callback);}}update.callback = callback;
}//使用requestEventTime返回eventTime对象;
function requestEventTime() {if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {// We're inside React, so it's fine to read the actual time.return now();} // We're not inside React, so we may be in the middle of a browser event.if (currentEventTime !== NoTimestamp) {// 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();return currentEventTime;
}//使用requestUpdateLane创建一个eventLane变量
function requestUpdateLane(fiber) {// Special casesvar mode = fiber.mode;if ((mode & ConcurrentMode) === NoMode) {return SyncLane;} else if ((executionContext & RenderContext) !== NoContext &&workInProgressRootRenderLanes !== NoLanes) {// 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.return pickArbitraryLane(workInProgressRootRenderLanes);}var isTransition = requestCurrentTransition() !== NoTransition;if (isTransition) {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.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.var eventLane = getCurrentEventPriority();return eventLane;
}
数据结构2:hostRootFiber.updateQueue属性
fiber.updateQueue属性在创建root的时候初始化(在类组件挂载使用该方法初始化),数据结构如下:
function initializeUpdateQueue(fiber) {var queue = {baseState: fiber.memoizedState,firstBaseUpdate: null,//单链表lastBaseUpdate: null,shared: {pending: null, //循环单项链表interleaved: null,//循环单项链表lanes: NoLanes //位掩码},effects: null};fiber.updateQueue = queue; //将创建的update对象赋值给fiber属性
}
一、root.render()方法
输入变量:
- children:组件
- root:根容器节点
无返回变量。
ReactDOMHydrationRoot.prototype.render = ReactDOMRoot.prototype.render = function (children) {var root = this._internalRoot;if (root === null) {throw new Error('Cannot update an unmounted root.');}{if (typeof arguments[1] === 'function') {error('render(...): does not support the second callback argument. ' + 'To execute a side effect after rendering, declare it in a component body with useEffect().');} else if (isValidContainer(arguments[1])) {error('You passed a container to the second argument of root.render(...). ' + "You don't need to pass it again since you already passed it to create the root.");} else if (typeof arguments[1] !== 'undefined') {error('You passed a second argument to root.render(...) but it only accepts ' + 'one argument.');}var container = root.containerInfo;if (container.nodeType !== COMMENT_NODE) {var hostInstance = findHostInstanceWithNoPortals(root.current);if (hostInstance) {if (hostInstance.parentNode !== container) {error('render(...): It looks like the React-rendered content of the ' + 'root container was removed without using React. This is not ' + 'supported and will cause errors. Instead, call ' + "root.unmount() to empty a root's container.");}}}}updateContainer(children, root, null, null);
};
二、updateContainer()方法
输入变量:
- element:root.render()的children变量,存入update.payload属性里。
- cotainer:等价于root
- parentCompoen:为null
- callback:为null
返回变量:
- lane(实际使用中并没有变量接收lane返回值)
function updateContainer(element, container, parentComponent, callback) {{onScheduleRoot(container, element);}var current$1 = container.current;var eventTime = requestEventTime();var lane = requestUpdateLane(current$1); {markRenderScheduled(lane);}var context = getContextForSubtree(parentComponent);if (container.context === null) {container.context = context;} else {container.pendingContext = context;}{if (isRendering && current !== null && !didWarnAboutNestedUpdates) {didWarnAboutNestedUpdates = true;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(current) || 'Unknown');}}var update = createUpdate(eventTime, lane); // Caution: React DevTools currently depends on this property// being called "element".update.payload = {element: element};callback = callback === undefined ? null : callback;if (callback !== null) {{if (typeof callback !== 'function') {error('render(...): Expected the last optional `callback` argument to be a ' + 'function. Instead received: %s.', callback);}}update.callback = callback;}var root = enqueueUpdate(current$1, update, lane);if (root !== null) {scheduleUpdateOnFiber(root, current$1, lane, eventTime);entangleTransitions(root, current$1, lane);}return lane;
}
三、enqueueUpdate()
将创建的update对象添加到update。
输入变量:
- fiber:container指向的current,也就是hostRootFiber
- update:createUpdate创建的update对象
- lane:var lane = requestUpdateLane(current$1) (具体方法见update对象)
返回变量:
- 返回enqueueConcurrentClassUpdate(fiber, sharedQueue, update, lane)
function enqueueUpdate(fiber, update, lane) {var updateQueue = fiber.updateQueue;if (updateQueue === null) {// Only occurs if the fiber has been unmounted.return null;}var sharedQueue = updateQueue.shared;{if (currentlyProcessingQueue === sharedQueue && !didWarnUpdateInsideUpdate) {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()) {// This is an unsafe render phase update. Add directly to the update// queue so we can process it immediately during the current render.var 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;}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);}
}
四、enqueueConcurrentClassUpdate()
输入变量:
- fiber:container指向的current,也就是hostRootFiber
- queue:enqueueUpdate()的sharedQueue变量,fiber.updateQueue的Shared属性。
- update:createUpdate创建的update对象
- lane:var lane = requestUpdateLane(current$1) (具体方法见update对象)
function enqueueConcurrentClassUpdate(fiber, queue, update, lane) {//取出当前队列的 interleaved 链表(循环链表的最后一个节点,或 null)。var interleaved = queue.interleaved;// 如果 interleaved 为空,说明这是本次渲染的第一个交错更新:// update.next = update;// 创建一个只有自己一个节点的循环链表。// pushConcurrentUpdateQueue(queue);// 把这个队列加入全局的 concurrentQueues,方便渲染结束时统一处理。// 如果 interleaved 不为空,说明已经有交错更新:// update.next = interleaved.next;// 新 update 指向链表头(第一个节点)。// interleaved.next = update;// 原链表尾(last)指向新 update,插入到链表头部。// 这样链表依然是循环的,且新 update 成为最后一个节点。if (interleaved === null) {update.next = update;pushConcurrentUpdateQueue(queue);} else {update.next = interleaved.next;interleaved.next = update;}//更新队列的 interleaved 指针,始终指向循环链表的最后一个节点。queue.interleaved = update;//追溯 fiber 到根节点,标记本次更新的优先级(lane),并返回根节点return markUpdateLaneFromFiberToRoot(fiber, lane);
}
五、pushConcurrentUpdateQueue()和markUpdateLaneFromeFiberToRoot()
markUpdateLaneFromeFiberToRoot():从当前 fiber(组件节点)向上遍历到根节点(HostRoot),沿途合并/标记本次更新的优先级(lane),并返回根节点(FiberRoot)。
输入变量:
- sourceFiber:container指向的current,也就是hostRootFiber
- lane:var lane = requestUpdateLane(current$1) (具体方法见update对象)
function mergeLanes(a, b) {return a | b;
}var concurrentQueues = null;
function pushConcurrentUpdateQueue(queue) {if (concurrentQueues === null) {concurrentQueues = [queue];} else {concurrentQueues.push(queue);}
}function markUpdateLaneFromFiberToRoot(sourceFiber, lane) {// 1. 当前 fiber 标记本次更新的 lanesourceFiber.lanes = mergeLanes(sourceFiber.lanes, lane);var alternate = sourceFiber.alternate;if (alternate !== null) {alternate.lanes = mergeLanes(alternate.lanes, lane);}// 2. 向上遍历父节点,沿途合并 childLanesvar 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);}node = parent;parent = parent.return;}// 3. 如果到达 HostRoot,返回根节点if (node.tag === HostRoot) {var root = node.stateNode;return root;} else {return null;}
}