上一篇文章 创建根节点 createRoot 介绍了 createRoot
函数,本文继续说下它的返回值,共三个:
- _internalRoot
- render
- unmount
其中 _internalRoot
在上一篇文章已经介绍,这里重点讲述 render
和 unmount
。
render 渲染
js
ReactDOMRoot.prototype.render = function(children: ReactNodeList): void {
const root = this._internalRoot;
updateContainer(children, root, null, null)
}
unmount 卸载
js
ReactDOMRoot.prototype.unmount = function(): void {
const root = this._internalRoot;
if(root !== null) {
this._internalRoot = null;
const container = root.containerInfo;
// flushSync 是 React 提供的 API,外部也可以用,参考 https://zh-hans.react.dev/reference/react-dom/flushSync
flushSync((() => {
updateContainer(null, root, null, null)
}))
unmarkContainerAsRoot(container) // 函数定义在上一篇文章中
}
}
可以看到,两者都有 updateContainer
函数,下面重点说下。
updateContainer 把子节点渲染到容器内
updateContainer
函数分为 4 步:
js
// 把子节点渲染到容器内
function (
element: ReactNodeList, // 子节点
container: OpaqueRoot // FiberRoot
): Lane {
// 1. 获取 current 和 lane
// 在 React 中,lane 是用于标识 update 优先级,可以理解为表示 update 的优先级的一种机制。每个 update 都会被分配一个或多个 lane,以确定其在更新队列中的优先级顺序。
const current = container.current // container 对应的 fiber
// 获取本次 update 对应的 lane
const lane = requestUpdateLane(current)
// 2. 创建 update
const update = createUpdate(lane)
update.payload = {element}
// 3. update 入队,将 update 加入到 fiber 的 updateQueue 中
const root = enqueueUpdate(current, update, lane);
if (root !== null) {
// 4. 调度更新
scheduleUpdateOnFiber(root, current, lane);
}
}
requestUpdateLane
js
function requestUpdateLane(fiber: Fiber): Lane {
// React 内部的一些 update,比如 flushSync、setState,会通过上下文变量来跟踪其优先级
const updateLane: Lane = (getCurrentUpdatePriority(): any);
if (updateLane !== NoLane) {
return updateLane
}
// React 外部的 update,根据事件类型,向当前环境获取对应的优先级
const eventLane: Lane = (getCurrentEventPriority(): any);
return updateLane
}
function getCurrentEventPriority(): EventPriority {
const currentEvernt = window.event;
if (currentEvernt === undefined) {
return DefaultEventPriority
}
return getEventPriority(currentEvernt.type)
}
export const DefaultEventPriority: EventPriority = DefaultLane // 页面初次渲染的 lane 是二进制的 32
let currentUpdatePriority: EventPriority = NoLane
function getCurrentUpdatePriority(): EventPriority {
return currentUpdatePriority
}
// 点击更新状态时触发
function setCurrentUpdatePriority(newPriority: EventPriority) {
currentUpdatePriority = newPriority
}
createUpdate
createRoot(root).render()
节点与类组件的 setState
、forceUpdate
阶段均会创建 update
render()
两次会创建两个 update
类组件中调用两次 setState
,也会创建两个 update
。
js
// const UpdateState = 0 // 页面初次渲染,类组件 setState
// const ReplaceState = 1 // 类组件
// const ForceState = 2 // 类组件
// const CaptureState = 3 // 类组件
type Update<State> = {
lane: Lane,
tag: 0 | 1 | 2 | 3,
payload: any, // 携带的参数。初次渲染是子节点;类组件比如 setState 的参数
callback: (() => nuxed) | null,
next: Update<State> | null // 单链表
}
const UpdateState = 0
function createUpdate(lane: Lane): Update<mixed> {
const update: Update<mixed> = {
lane,
tag: UpdateState,
payload: null,
callback: null,
next: null
}
return update
}
enqueueUpdate
createRoot(root).render()
阶段与类组件的 setState
、forceUpdate
阶段最开始调用的是 enqueueUpdate
。
js
type SharedQueue = {
pending: Update<State> | null, // 单向循环链表,尾节点 -> 头节点
lanes: Lanes // 多个 update 合并成一个
}
function enqueueUpdate<State>(
fiber: Fiber,
update: Update<State>,
lane: Lane
) {
const updateQueue = fiber.updateQueue
if (updateQueue === null) {
// 只发生在 fiber 卸载时
return null
}
const sharedQueue: SharedQueue<State> = (updateQueue: any).shared
return enqueueConcurrentClassUpdate(fiber, sharedQueue, update, lane)
}
type ConcurrentUpdate = {
next: ConcurrentUpdate,
lane: Lane
}
type ConcurrentQueue = {
pending: ConcurrentUpdate | null
}
// 如果渲染正在进行中,并且收到来自并发事件的更新,我们会等到当前的渲染结束(无论是完成还是被中断)之后再将其添加到 fiber 队列中。
// 将其推送到这个数组中,这样我们以后就可以访问 queue、fiber、update 等。
const concurrentQueues: Array<any> = []
let concurrentQueuesIndex = 0;
let concrrentlyUpdatedLanes: Lanes = NoLanes;
function enqueueConcurrentClassUpdate<State>(
fiber: Fiber,
queue: ClassQueue<State>,
update: ClassUpdate<State>,
lane: Lane
): FiberRoot | null {
const concurrentQueue: ConcurrentQueue = (queue: any)
const concurrentUpdate: ConcurrentUpdate = (update: any)
// 1. update 入队
enqueueUpdate(fiber, concurrentQueue, concurrentUpdate, lane)
// 2. 返回 FiberRoot
return getRootForUpdateFiber(fiber)
}
function enqueueUpdate(
fiber: Fiber,
queue: ConcurrentQueue | null,
update: ConcurrentUpdate | null,
lane: Lane
) {
concurrentQueues[concurrentQueuesIndex++] = fiber
concurrentQueues[concurrentQueuesIndex++] = queue
concurrentQueues[concurrentQueuesIndex++] = update
concurrentQueues[concurrentQueuesIndex++] = lane
concrrentlyUpdatedLanes = mergeLanes(concrrentlyUpdatedLanes, lane)
fiber.lanes = mergeLanes(fiber.lanes, lane)
const alternate = fiber.alternate
// 如果老节点非空
if (alternate !== null) {
alternate.lanes = mergeLanes(alternate.lanes, lane)
}
}
function mergeLanes(a: Lanes | Lane, b: Lanes | Lane): Lanes {
return a | b
}
scheduleUpdateOnFiber
页面初次渲染、类组件 setState/forceUpdate
、函数组件 useState/useReducer
都会走到更新,都会调用 scheduleUpdateOnFiber
函数。
js
function _markRootUpdated(root: FiberRoot, updatedLane: Lane) {
root.pendingLanes |= updatedLane
// 如果 update 是 idle 的,将不会处理它,因为我们直到所有常规 update 完成后才会处理 idle 更新
if(updatedLane !== IdleLane) {
root.suspendedLanes = NoLandex
root.pingedLanes = NoLanes
}
}
// 标记根节点有一个 pending update,即待处理的更新。
function markRootUpdated(root: FiberRoot, updatedLanes: Lanes) {
_markRootUpdated(root, updatedLanes)
if (enableInfiniteRenderLoopDetection) {
// 如果循环超过限制次数(类组件 50 次,函数组件 25 次),抛出错误。比如在类组件的 render 函数里执行 setState
throwIfInfiniteUpdateLoopDetected()
}
}
function scheduleUpdateOnFiber(
root: FiberRoot, // 从根节点开始更新:具体的更新是在子节点上发生的,但是遍历是从根节点开始的
fiber: Fiber,
lane: Lane
) {
markRootUpdated(root, lane)
}