Fiber是什么
Fiber可以看做是Reat中最小的工作单元,它把更新过程碎片化。Fiber与Scheduler配合实现了任务的中断与恢复:当在执行任务过程中,遇到更高优先级的任务,则会中断当前任务,先执行高优先级的任务,高优先级任务执行完成后,再回过头来继续执行被中断的任务。
关于Scheduler可以看之前的文章React源码解析之Scheduler
在更新过程中,Fiber会根据React element深度遍历创建Fiber树,每个React元素都会对应一个Fiber对象:
React Element树: Fiber树:
APP APP
↓ ↓
div div
↓ ↓ ↓ ↓
h1 h2 h1 h2
Fiber对象的结构如下:
function FiberNode(
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode,
) {
// Instance
this.tag = tag; // 当前fiber的类型:ClassComponent、FunctionComponent、HostComponent...
this.key = key;
this.elementType = null; // 类组件指向的是组件的类, HostComponent指向的是标签类型:div、span...
this.type = null; // 与elementType一样,类组件指向的是组件的类, HostComponent指向的是标签类型:div、span...
this.stateNode = null; // fiber的实例,类组件指向的是组件实例,HostComponent指向的是dom元素
// Fiber 链表
this.return = null; // 指向父级fiber
this.child = null; // 指向子级fiber
this.sibling = null; // 指向兄弟fiber
this.index = 0;
this.ref = null; // ref相关
// 更新相关
this.pendingProps = pendingProps; // 将要执行更新的属性
this.memoizedProps = null; // 上一次更新的props
this.updateQueue = null; // 更新队列
this.memoizedState = null; // 上一次更新的state
this.dependencies = null;
this.mode = mode;
// Effects
this.flags = NoFlags;
this.subtreeFlags = NoFlags;
this.deletions = null;
// 和lanes优先级相关
this.lanes = NoLanes; // 表示当前节点是否需要更新
this.childLanes = NoLanes; // 表示当前节点的子节点是否需要更新
// current树和workInProgress树中节点相互关联的属性
// 可以用于判断是否需要更新还是创建,有值表示更新,反之则需要创建
this.alternate = null;
}
双缓冲树的概念
在React更新过程中会有两颗树,一颗树叫做current树,一颗树叫做workInprogress树。
current树对应了屏幕上已经渲染好的内容,workInprogress树是根据current树深度优先遍历出来新的fiber树,所有需要更新的内容都会在workInprogress树上体现。当更新未完成时,屏幕上始终都只会显示current树对应的内容,当更新完成则会将current树切换为workInprogress树,此时workInprogress树则变成新的current树。
构建Fiber树
下面我们根据源码,来看看是如何构建Fiber树的。
我们从入口函数开始:
ReactDOM.createRoot(root).render(<Demo1 />);
首先调用了createRoot函数,直接进入源码查看:
export function createRoot(
container: Container,
options?: CreateRootOptions,
): RootType {
...
// 创建容器-Fiber根节点
const root = createContainer(
container,
ConcurrentRoot,
hydrate,
hydrationCallbacks,
isStrictMode,
concurrentUpdatesByDefaultOverride,
);
...
return new ReactDOMRoot(root);
}
只看与创建Fiber相关的代码,可以看到调用了createContainer函数:
export function createContainer(
containerInfo: Container,
tag: RootTag,
hydrate: boolean,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
isStrictMode: boolean,
concurrentUpdatesByDefaultOverride: null | boolean,
): OpaqueRoot {
return createFiberRoot(
containerInfo,
tag,
hydrate,
hydrationCallbacks,
isStrictMode,
concurrentUpdatesByDefaultOverride,
);
}
createContainer函数内部有调用了createFiberRoot函数:
export function createFiberRoot(
containerInfo: any,
tag: RootTag,
hydrate: boolean,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
isStrictMode: boolean,
concurrentUpdatesByDefaultOverride: null | boolean,
): FiberRoot {
// 创建fiber root根节点,在react项目中fiber root根节点只存在一个
const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);
// 创建root fiber节点,可以是多个
const uninitializedFiber = createHostRootFiber(
tag,
isStrictMode,
concurrentUpdatesByDefaultOverride,
);
// 关联fiber root和root fiber
root.current = uninitializedFiber;
uninitializedFiber.stateNode = root;
...
// 初始化root fiber上的更新队列
initializeUpdateQueue(uninitializedFiber);
return root;
}
可以看到首先创建了一个FiberRootNode实例,然后又调用createHostRootFiber函数,这个函数的作用是创建了一个FiberNode的实例并返回,然后在FiberRoot实例上使用current属性关联了FiberNode实例,FiberNode实例上使用stateNode属性关联了FiberRoot实例,我们将FiberRootNode的实例叫做fiber root,FiberNode实例叫做root fiber,因为后续调用render方法渲染的内容都会挂载到root fiber下,所以我们可以把root fiber看做是render内容的根节点。
在react项目中fiber root根节点只存在一个。
它们的关系如下:
最后将fiber root返回,然后在createRoot中拿到返回的fiber root,将它作为参数创建了一个ReactDOMRoot实例。
那我们来看一下ReactDOMRoot中的源码:
function ReactDOMRoot(internalRoot) {
this._internalRoot = internalRoot;
}
ReactDOMRoot.prototype.render = function(children: ReactNodeList): void {
const root = this._internalRoot; // fiber root
...
updateContainer(children, root, null, null);
};
可以看到ReactDOMRoot中将fiber root赋值给了私有属性_internalRoot。
在ReactDOMRoot的原型上有个render方法,也就是调用完createRoot方法后,链式调用的render方法:
ReactDOM.createRoot(root).render(<Demo1 />);
可以看到render方法中调用了updateContainer函数,传入了children,也就是我们调用render方法时,传入的需要渲染的React element对象。
到这里基础的Fiber树就创建出来了。
后面就是开始创建任务,使用Scheduler进行任务调度,具体可以看我另二篇文章React源码解析之优先级Lane模型上 和 React源码解析之Scheduler。
创建WorkInprogress
当任务开始执行时,会调用prepareFreshStack函数,而这个函数的作用则是创建workInprogress树:
function prepareFreshStack(root: FiberRoot, lanes: Lanes) {
...
workInProgress = createWorkInProgress(root.current, null);
...
}
可以这个函数中调用createWorkInProgress,将root.current作为参数传入,创建了workInProgress。root则是fiber root,root.current则是root fiber。
那我们进入createWorkInProgress函数中看一下,是如何创建workInProgress的:
export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
let workInProgress = current.alternate;
if (workInProgress === null) {
workInProgress = createFiber(
current.tag,
pendingProps,
current.key,
current.mode,
);
workInProgress.elementType = current.elementType;
workInProgress.type = current.type;
workInProgress.stateNode = current.stateNode;
...
workInProgress.alternate = current;
current.alternate = workInProgress;
} else {
workInProgress.pendingProps = pendingProps;
workInProgress.type = current.type;
workInProgress.flags = NoFlags;
workInProgress.subtreeFlags = NoFlags;
workInProgress.deletions = null;
...
}
...
workInProgress.flags = current.flags & StaticMask;
workInProgress.childLanes = current.childLanes;
workInProgress.lanes = current.lanes;
workInProgress.child = current.child;
workInProgress.memoizedProps = current.memoizedProps;
workInProgress.memoizedState = current.memoizedState;
workInProgress.updateQueue = current.updateQueue;
const currentDependencies = current.dependencies;
workInProgress.dependencies =
currentDependencies === null
? null
: {
lanes: currentDependencies.lanes,
firstContext: currentDependencies.firstContext,
};
workInProgress.sibling = current.sibling;
workInProgress.index = current.index;
workInProgress.ref = current.ref;
...
return workInProgress;
}
可以看到,首先获取当前current树上fiber节点的alternate属性,也就是workInProgress上对应的fiber节点。如果workInProgress的fiber节点不为空,则直接复用,并会更新pendingProps,pendingProps也就是本次更新需要更新的内容。而为空则会重新创建一个新的fiber对象,并且与current树上的fiber节点使用alternate属性相互关联。
alternate的作用是可以在下一次更新的时候,判断alternate是否有值,有则说明是更新,无则是创建。
此时的Fiber树,是这样的:
Fiber的更新
在React更新的过程中,共分为两个阶段:
- render阶段
- commit阶段 render阶段是可以将中断的,而在commit阶段是不可中断的。
在render阶段每个Fiber节点又都会经历两个阶段:
- beginWork阶段
- completeWork阶段
beginWork阶段的主要工作,则是从root fiber开始向下深度遍历构建workInprogress树,进行diff算法确定需要更新的fiber的最终状态。
completeWork阶段主要工作,则是从深度遍历的最后一个节点开始向上回溯,并会检查是否有兄弟节点有则会再次执行beginWork流程,没有则会向上回溯。在回溯过程中,将需要更新fiber的属性收集起来挂载到fiber节点下的更新队列updateQueue上,并且添加更新的flags。每次回溯都将子节点的flags挂载到父节点的subtreeFlags上。
接下来将分别对beginWork和completeWork的源码解析写成单独的章节。