序
本章主要来了解Fiber节点是如何被创建并构建Fiber树的,
render阶段
- 开始于
performSyncWorkOnRoot或performConcurrentWorkOnRoot方法的调用 - 取决于本次更新是
同步更新还是异步更新
// performSyncWorkOnRoot会调用该方法
function workLoopSync(){
while(workInProgress!==null){
performUnitOfWork(workInProgress)
}
}
// performConcurrentWorkOnRoot会调用该方法
function workLoopConcurrent(){
while(workInProgress!==null&&!shouldYield()){
performUnitOfWork(workInProgress)
}
}
唯一的区别是shouldYield,如果当前浏览器帧没有剩余时间,shouldYield会中止循环,直到浏览器有空闲时间后再继续遍历
workInProgress代表当前已创建的workInProgress fiber
performUnitOfWork方法会创建下一个Fiber节点并赋值给workInProgress,并将workInProgress与已创建的Fiber节点连接起来构成Fiber树
由于Fiber Reconciler是从Stack Reconciler重构而来,通过遍历的方式实现可中断的递归,所以performUnitOfWork的工作可以分为两部分:递和归
递阶段
首先从rootFiber开始向下深度优先遍历,为遍历到的每个Fiber节点调用beginWork方法
该方法会根据传入的Fiber节点创建子Fiber节点,并将这两个Fiber节点连接起来
当遍历到叶子节点(即没有子组件的组件)时,就会进入归阶段
归阶段
在归阶段会调用completeWork处理Fiber阶段
当某个Fiber节点执行完completeWork,如果其存在兄弟Fiber节点(即fiber.sibling!==null),会进入其兄弟Fiber的递阶段
如果不存在兄弟Fiber,会进入父级Fiber的归阶段
递和归阶段会交错执行直到归到rootFiber,至此,render阶段的工作就结束了
例子
function App(){
return(
<div>
i am
<span>KaSong</span>
</div>
)
}
ReactDOM.render(<App />,document.getElementById('root'))
对应的Fiber树结构:
render阶段依次执行:
rootFiber beginWork
App Fiber beginWork
div Fiber beginWork
'i am' Fiber beginWork
'i am' Fiber completeWork
span Fiber beginWork
span Fiber completeWork
div Fiber completeWork
App Fiber completeWork
rootFiber completeWork
实现beginWork
beginWork的工作是传入当前Fiber节点,创建子Fiber节点
workInProgress:当前组件对应的Fiber节点
除rootFiber以外,组件mount时,由于是首次渲染,是不存在当前组件对应的Fiber节点在上一次更新时的Fiber节点,即mount时current===null
组件update时,由于之前已经mount过,所以current!==null
所以可以通过current===null?来区分组件是处于mount还是update
基于此原因,beginWork的工作可以分为两部分:
update时:如果current存在,在满足一定条件时可以复用current节点,这样就能克隆current.child作为workInProgress.child,而不需要新建workInProgress.childmount时:除fiberRootNode以外,current===null,会根据fiber.tag不同,创建不同类型的子Fiber节点
目前只实现mount时的beginWork
beginWork接收一个参数wip,即当前组件对应的Fiber节点- 根据Fiber上的
tag匹配需要执行的更新方法
// 递归中的递阶段
export const beginWork = (wip: FiberNode) => {
// 比较,返回子fiberNode
switch (wip.tag) {
case HostRoot:
return updateHostRoot(wip);
case HostComponent:
return updateHostComponent(wip);
case HostText:
return null;
default:
if (__DEV__) {
console.warn('beginWork未实现的类型');
}
break;
}
return null;
};
function updateHostRoot(wip: FiberNode) {
const baseState = wip.memoizedState;
const updateQueue = wip.updateQueue as UpdateQueue<Element>;
const pending = updateQueue.shared.pending;
updateQueue.shared.pending = null;
const { memoizedState } = processUpdateQueue(baseState, pending);
wip.memoizedState = memoizedState;
const nextChildren = wip.memoizedState;
reconcileChildren(wip, nextChildren);
return wip.child;
}
function updateHostComponent(wip: FiberNode) {
const nextProps = wip.pendingProps;
const nextChildren = nextProps.children;
reconcileChildren(wip, nextChildren);
return wip.child;
}
对于常见的组件类型FunctionComponent/ClassComponent/HostComponent最终会进入reconcileChildren方法
reconcileChildren
- 对于
mount的组件,会创建新的子Fiber节点 - 对于
update的组件,会将当前组件与该组件在上次更新时对应的Fiber节点比较(Diff),将比较的结果生成新的Fiber节点
function reconcileChildren(wip: FiberNode, children?: ReactElementType) {
const current = wip.alternate;
if (current !== null) {
// update
wip.child = reconcileChildFibers(wip, current?.child, children);
} else {
// mount
wip.child = mountChildFibers(wip, null, children);
}
}
不论走mount还是update逻辑,最终都会生成新的Fiber节点并赋值给workInProgress.child,作为本次beginWork返回值,并作为下次performUnitOfWork执行时workInProgress的传参
实现completeWork
类似beginWork,completeWork也是针对不同fiber.tag调用不同的处理逻辑
import {
appendInitialChild,
createInstance,
createTextInstance
} from 'hostConfig';
import { FiberNode } from './fiber';
import { NoFlags } from './fiberFlags';
import { HostRoot, HostText, HostComponent } from './workTags';
export const completeWork = (wip: FiberNode) => {
// 递归中的归
const newProps = wip.pendingProps;
const current = wip.alternate;
switch (wip.tag) {
case HostComponent:
if (current !== null && wip.stateNode) {
// update
} else {
// 1. 构建DOM
const instance = createInstance(wip.type, newProps);
// 2. 将DOM插入到DOM树中
appendAllChildren(instance, wip);
wip.stateNode = instance;
}
bubbleProperties(wip);
return null;
case HostText:
if (current !== null && wip.stateNode) {
// update
} else {
// 1. 构建DOM
const instance = createTextInstance(newProps.content);
wip.stateNode = instance;
}
bubbleProperties(wip);
return null;
case HostRoot:
bubbleProperties(wip);
return null;
default:
if (__DEV__) {
console.warn('未处理的completeWork情况', wip);
}
break;
}
};
function appendAllChildren(parent: FiberNode, wip: FiberNode) {
let node = wip.child;
while (node !== null) {
if (node.tag === HostComponent || node.tag === HostText) {
appendInitialChild(parent, node?.stateNode);
} else if (node.child !== null) {
node.child.return = node;
node = node.child;
continue;
}
if (node === wip) {
return;
}
while (node.sibling === null) {
if (node.return === null || node.return === wip) {
return;
}
node = node?.return;
}
node.sibling.return = node.return;
node = node.sibling;
}
}
function bubbleProperties(wip: FiberNode) {
let subtreeFlags = NoFlags;
let child = wip.child;
while (child !== null) {
subtreeFlags |= child.subtreeFlags;
subtreeFlags |= child.flags;
child.return = wip;
child = child.sibling;
}
wip.subtreeFlags |= subtreeFlags;
}
mount时
mount时的主要逻辑包括三个:
- 为
Fiber节点生成对应的DOM节点 - 将子孙
DOM节点插入刚生成的DOM节点中 - 与
update逻辑中的updateHostComponent类似的处理props的过程
const currentHostContext = getHostContext();
// 为fiber创建对应DOM节点
const instance = createInstance(
type,
newProps,
rootContainerInstance,
currentHostContext,
workInProgress,
);
// 将子孙DOM节点插入刚生成的DOM节点中
appendAllChildren(instance, workInProgress, false, false);
// DOM节点赋值给fiber.stateNode
workInProgress.stateNode = instance;
// 与update逻辑中的updateHostComponent类似的处理props的过程
if (
finalizeInitialChildren(
instance,
type,
newProps,
rootContainerInstance,
currentHostContext,
)
) {
markUpdate(workInProgress);
}
由于completeWork属于归阶段调用的函数,每次调用appendAllChildren时都会将已生成的子孙DOM节点插入当前生成的DOM节点下,那么当归到rootFiber时,我们已经有一个构建好的离屏DOM树
所有代码: