示例代码:
class App extends React.Component {
componentDidMount() {
console.log('App Mount');
console.log(`App组对应的fiber节点:`,this._reactInternals);
}
render() {
return (
<div className="app">
<header>header</header>
<Content />
</div>
);
}
}
class Content extends React.Component {
componentDidMount() {
console. log('Content Mount');
console.log(`Content组应的fiber节:`, this._reactInternals);
}
render() {
return(
<React. Fragment>
<p>1</p>
<p>2</p>
</React. Fragment>
);
}
}
export default App;
根据这个结构,可以在控制台中打出当前页面对应的fiber树(用于观察其结构):
document.getElementByld('root')._reactRootContainer._internalRoot.current;然后进入react-reconciler包调用updateContainer函数:
// ... 省略了部分代码
export function updateContainer(
element: ReactNodeList,
container: OpaqueRoot,
parentComponent: ?ReactSComponent<any, any>,
callback: ?Function,
): Lane {
// 获取当前时间戳
const current = container.current;
const eventTime = requestEventTime();
// 1.创建一个优先级变量(车遵模型)
const lane = requestUpdateLane(current);
// 2.根据车道优先级,创建update对象,并加入fiber.updateQueue.pending队列
const update = createUpdate(eventTime, lane);
update.payload = { element };
callback = callback === undefined ? null : callback;
if (callback !== null) {
update.callback = callback;
}
enqueueUpdate(current, update);
// 3. 进入reconciler运作流程中的`输入环节
scheduleUpdateOnFiber(current, lane, eventTime);
return lane;
}
由于 update 对象的创建,此时的内存结构如下
HostRootFiber.updateQueue.shared.pending.payload.element 中,
在 scheduleUpdateOnFiber 函数中
//...省略部分代码
export function scheduleUpdateOnFiber(
fiber: Fiber,
lane:Lane,
eventTime: number,
) {
// 标记优先级
const root = markUpdateLaneFromFiberToRoot(fiber, lane);
if(lane === SyncLane) {
if(
(executionContext & LegacyUnbatchedContext) !== NoContext &&
(executionContext & (RenderContext CommitContext)) === NoContext
) {
// 首次渲染,直接进行fiber构造
performSyncWorkOnRoot(root);
}
// ...
}
}
可以看到,在Legacy模式下且首次渲染时
有2个函数 markUpdateLaneFromFiberToRoot 和 performSyncWorkOnRoot
其中 markUpdateLaneFromFiberToRoot(fiber,lane)函数在fiber树构造(对比更新)中才会发挥作用
因为在初次创建时并没有与当前页面所对应的fiber树,所以核心代码并没有执行,最后直接返回了FiberRoot对象
performSyncWorkOnRoot看起来源码很多,初次创建中真正用到的就2个函数:
function performSyncWorkOnRoot(root) {
let lanes;
let exitStatus;
if (
root === workInProgressRoot &&
includesSomeLane(root.expiredLanes, workInProgressRootRenderLanes)
) {
// 初次构造时(因为root = fiberRoot,workInProgressRoot=null),所以不会进入
} else {
// 1. 获取本次render的优先级,初次构造返回 NoLanes
lanes = getNextLanes(root, NoLanes);
// 2. 从root节点开始,至上而下更新
exitStatus = renderRootSync(root, lanes);
}
// 将最新的fiber树挂载到root.finishedWork节点上
const finishedWork: Fiber = (root.current.alternate: any);
root.finishedWork = finishedWork;
root.finishedlanes = lanes;
// 进入commit阶段
commitRoot(root);
//...后面的内容跳过
}
其中 getNextLanes 返回本次 render 的渲染优先级中
renderRootSync
function renderRootSync(root: FiberRoot, lanes: Lanes) {
const prevExecutionContext = executionContext;
executionContext |= RenderContext;
// 如果fiberRoot变动,或者update.Lone变动,都会刷新栈帧,丢弃上一次渲染进度
if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
// 刷新栈帧,legacy模式下都会进入
prepareFreshStack(root,lanes);
}
do {
try {
workLoopSync();
break;
} catch (thrownValue) {
handleError(root, thrownValue);
}
} while (true);
executionContext = prevExecutionContext;
// 重置全局变量,表明render束
workInProgressRoot = null;
workInProgressRootRenderLanes = NoLanes;
return workInProgressRootExitStatus;
}
在 renderRootSync 中,在执行fiber树构造前(workLoopSync)会先刷新栈帧
prepareFreshStack 在这里创建了 HostRootFiber.alternate
重置局变量 workInProgress 和 workInProgressRoot 等
逻辑来到 workLoopSync, 绥安本节在 Legacy 模式下进行讨论
此处还是对比一下 workLoopConcurrent
function workLoopSync() {
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
function workLoopConcurrent() {
// Perform work until Scheduler asks us to yield
while (workInProgress !== null && !shouldYield()) {
performUnitOfwork(workInProgress);
}
}
可以看到workLoopConcurrent相比于Sync,会多一个停顿机制
这个机制实现了时间切片和可中断染
结合 performUnitOfWork 函数
// ...省略部分无关代码
function performUnitOfWork(unitofwork: Fiber): void {
// unitOfWork 就是被传入的 workInProgress
const current = unitOfWork.alternate;
let next;
next = beginWork(current, unitOfWork, subtreeRenderLanes);
unitOfWork.memoizedProps = unitOfWork.pendingProps;
if (next === null) {
// 如果没有派生出新的节点,则进入completeWork阶段,传入的是当前unitOfWork
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}
}
可以明显的看出,整个fiber树构造是一个深度优先遍历其中有2个重要的变量workInProgress和current(双缓冲技术)
current = workInProgress.alternate(即fiber.alternate), 指向当前页面正在使用的fiber节点.在深度优先遍历中,每个 fiber 节点都会经历2个阶段
这2个阶段共同完成了每一个fiber节点的创建,所有fiber节点则构成了fiber树.
beginWork(current, unitOfWork, subtreeRenderLanes)
针对所有的 Fiber 类型,其中的每一个 case 处理一种 Fiber 类型.
updateXXX函数(如:updateHostRoot, updateClassComponent等)的主要逻辑:
1.根据 ReactElement对象创建所有的fiber节点,最终构造出fiber树形结构(设置 return和sibling指针)
2.设置 fiber.flags (二进制形式变量,用来标记 fiber节点的增,删,改状态,等待completeWork阶段处理)
3.设置 fiber.stateNode 局部状态(如Class类型节点:fiber.stateNode=new Class())
function beginwork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {
const updateLanes = workInProgress.lanes;
if (current !== null) {
// update逻辑,首次render不会进入
} else {
didReceiveUpdate = false;
}
// 1.设置workInProgress优先级为NoLanes(最高优先级)
workInProgress.lanes = NoLanes;
// 2.根据workInProgress节点的类型,用不同的方法派生出子节点
switch (
workInProgress.tag //了本例使用到case
) {
case ClassComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress. pendingProps;
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
return updateclassComponent(
current,
workInProgress,
Component,
resolvedProps,
renderLanes,
);
}
case HostRoot:
return updateHostRoot(current, workInProgress, renderLanes);
case HostComponent:
return updateHostComponent(current, workInProgress, renderLanes);
case HostText:
return updateHostText(current, workInProgress);
case Fragment:
return updateFragment(current, workInProgress, renderLanes);
}
}
updateXXX函数(如: updateHostRoot, updateClassComponent等)虽然case较多
但是主要逻辑可以概括为3个步骤
不同的updateXXX函数处理的fiber节点类型不同总的目的是为了向下生成子节点
在这个过程中把一些需要持久化的数据挂载到fiber节点上
如fiber.stateNode,fiber.memoizedState等把fiber节点的特殊操作设置到fiber.flags
如:节点ref,class组件的生命周期,function组件的hook,节点删除等
这里列出updateHostRoot,updateHostComponent的代码,对于其他常用case的分析
如class类型,function类型
fiber树的根节点是 HostRootFiber 节点
所以第一次进入beginWork会调用updateHostRoot(current, worklnProgress, renderLanes)
// 省略无关代码
function updateHostRoot(current, workInProgress, renderlanes) {
// 1、状态计算,更新整合到workInProgress.memoizedState中来
const updateQueue = workInProgress.updateQueue;
const nextProps = workInProgress.pendingProps;
const prevState = workInProgress.memoizedState;
const prevChildren = prevState !== null ? prevState.element : null;
cloneUpdateQueue(current, workInProgress);
//遍历updateQueue.shared.pending,提取有足够优先级的update对象,计算出最终的状态 workInProgres.
processUpdateQueue(workInProgress, nextProps, null, renderLanes);
const nextState = workInProgress.memoizedState;
// 2.获取下级'ReactElement"对象
const nextChildren = nextState.element;
const root: FiberRoot = workInProgress.stateNode;
if (root.hydrate && enterHydrationState(workInProgress)) {
//..服务端渲染相关,此处省路
} else {
// 3.根据'ReactElement"对象,调用 reconcileChildren'生成"Fiber"子节点(只生成"次级子节点")
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
}
return workInProgress.child;
}
普通DOM标签类型的节点(如div,span,p), 会进入 updateHostComponent:
// ...省略部分无关代码
function updateHostComponent(
current: Fiber| null,
workInProgres: Fiber,
renderLanes: Lanes,
) {
// 1. 状态计算,由于HostComponent是无状态组件,所以只需要收集 nextProps即可,它没有 memoizedState
const type = workInProgress.type;
const nextProps = workInProgress.pendingProps;
const prevProps = current !== null ? current.memoizedProps : null;
// 2. 获取下级ReactElement对象
let nextChildren = nextProps.children;
const isDirectTextChild = shouldSetTextContent(type, nextProps);
if(isDirectTextChild) {
// 如果子节点只有一个文本节点,不用再创建一个HostText类型的fiber
nextChildren = null;
} else if (prevProps != null && shouldSetTextContent(type, prevProps)) {
// 特殊操作需要设置fiber.flags
workInProgress.flags |= ContentReset;
}
// 特殊操作需要设置fiber.flags
markRef(current, workInProgress);
// 3. 根据`ReactElement'对象,调用`reconcileChildren'生成Fiber`子节点(只生成`次级子节点`)
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
completeUnitOfWork(unitOfWork) 处理 beginWork 阶段已经创建出来的 fiber 节点
核心逻辑
1.调用completeWork
2.把当前fiber对象的副作用队列(firstEffect 和 lastEffect)添加到父节点的副作用队列之后
3.识别beginWork阶段设置的fiber.flags
function completeUnitOfWork(unitOfWork: Fiber): void {
let completedWork = unitOfWork;
// 外层循环控制并移动指针(`workInProgress',`completedWork"等)
do {
const current = completedWork.alternate;
const returnFiber = completedWork.return;
if ((completedWork.flags & Incomplete) === NoFlags) {
let next;
// 1.处理Fiber节点,会调用渲染器(调用react-dom包,关联Fiber节点和dom对象,绑定事件等)
next = completeWork(current, completedWork, subtreeRenderLanes); //处理单个节点
if (next !== null) {
// 如果派生出其他的子节点,则回到`beginWork"阶段进行处理
workInProgress = next;
return;
}
// 重置子节点的优先级
resetChildLanes(completedWork);
if (
returnFiber !== null &&
(returnFiber.flags & Incomplete) === NoFlags
) {
// 2.收集当前Fiber节点以及其子树的副作用effects
// 2.1把子节点的副作用队列添加到父节点上
if (returnFiber.firstEffect == null) {
returnFiber.firstEffect = completedWork.firstEffect;
}
if (completedWork.lastEffect !== null) {
if (returnFiber.lastEffect !== null) {
returnFiber.lastEffect.nextEffect = completedWork.firstEffect;
}
returnFiber.lastEffect = completedWork.lastEffect;
}
// 2.2如果当前fiber节点有副作用,将其添加到子节点的副作用队列之后.
const flags = completeWork.flags;
if (flags > PerformedWork) {
// PerformedWork是提供给 React DevTools读取的,所以略过PerformedWork
if (returnFiber.lastEffect !== null){
returnFiber.lastEffect.nextEffect = completedWork;
} else {
returnFiber.firstEffect = completedWork;
}
returnFiber.lastEffect= completedWork;
}
} else {
// 异常处理,本节不讨论
}
const siblingFiber = completedWork.sibling;
if (siblingFiber !== null) {
// 如果有兄弟节点,返回之后再次进入`beginWork`阶段
workInProgress = siblingFiber;
return;
}
// 移动指针,指向下一个节点
completedWork = returnFiber;
workInProgress = completedWork;
} while (completedWork !== null);
// 已回溯到根节点,设置workInProgressRootExitStatus = RootCompleted
if (workInProgressRootExitStatus === RootIncomplete) {
workInProgressRootExitStatus = RootCompleted;
}
}
fiber 处理函数 completeWork
function completeWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {
const newProps = workInProgress.pendingProps;
switch(workInProgress.tag) {
case ClassComponent: {
// Class类型不做处理
return null;
}
case HotRoot: {
const fiberRoot = (workInProgress.stateNode: FiberRoot);
if (fiberRoot.pendingContext) {
fiberRoot.context = fiberRoot.pendingContext;
fiberRoot.pendingContext= null;
}
if (current === null || current.child == null){
// 设置fiber.flags记
workInProgress.flags = Snapshot;
}
return null;
}
case HostComponent: {
popHostContext(workInProgress);
const rootContainerInstance = getRootHostContainer();
const type = workInProgress.type;
if (current !== null && workInProgress.stateNode !== null) {
// update逻辑,初次render不会进入
} else {
const currentHostContext = getHostContext();
// 1.创建DOM对象
const instance = createInstance(
type,
newProps,
rootContainerInstance,
currentHostContext,
workInProgress,
);
// 2、把子树中的DOM对象append到本节点的DOM对象之后
appendAllChildren(instance, workInProgress, false, false);
// 设置stateNode届性,指向DOM对象
workInProgress.stateNode = instance;
if(
// 3,设置DOM对象的属性,绑定事件等
finalizeInitialChildren(
instance,
type,
newProps,
rootContainerInstance,
currentHostContext,
)
) {
// 设置fiber. flags标(Update)
markUpdate(workInProgress);
}
if (workInProgress.ref !== null) {
//设置fiber.flags标(Ref)
markRef(workInProgress);
}
return null;
}
}
}
}
可以看到在满足条件的时候也会设置 fiber.flags, 所以设置 fiber.flags 并非只在 beginWork 阶段
class App extends React.Component {
componentDidMount() {
console.log('App Mount');
console.log(`App组对应的fiber节点:`, this._reactInternals);
}
render() {
return(
<div className="app">
<header>header</header>
<Content />
</div>
);
}
}
class Content extends React.Component {
componentDidMount() {
console. log('Content Mount');
console.log(`Content组应的fiber节点:`, this._reactInternals);
}
render() {
return(
<React.Fragment>
<p>1</p>
<p>2</p>
</React.Fragment>
);
}
}
export default App;
fiber( ), 同时设置子节点 (fiber( ))fiber.flags |= Placementfiber( )fiber( )
fiber( )节点,此时 current = nullfiber( )节点, workInProgress.flags |= UpdateworkInProgress.flags |= PerformedWorkclassInstance.render()在本步骤执行后,虽然返回了 render 方法中所有的ReactElement对象
completeUnitOfWork 执行前:
completeUnitOfWork 执行过程:
第1次循环
fiber( )
fiber( )节点class类型的节点,与第2次调用逻辑一致fiber(p),移动workInProgress指针指向子节点fiber(p)
fiber( )
fiber( )的flags标志位有改动( completedWork.flags > PerformedWork)
fiber(div)节点对应的DOM实例,并append子树节点的DOM实例fiber(div)的副作用队列不为空,将其拼接到父节点fiber的副作用队列后面fiber( )
fiber( )的副作用队列不为空fiber(HostRootFiber)的副作用队列上fiber( )的flags标志位有改动(completedWork.flags > Performedwork)fiber(HostRootFiber)
workinProgress.flags |= SnapshotworkInProgress = null
workInProgressRoot = nullfiberRoot.finishedwork 上