由于最近半年的工作涉猎到react,因此最近一个月花了点时间了解react的核心原理,发现Fiber的设计很是精巧,因此做一篇记录。
Why
在React v15前,每次有更新发生时,react的Reconciler(协调器)会做如下一些事情
1、调用组件的render方法获取当前的Virtual Dom
2、将当前的Virtual Dom和上次更新的Virtual Dom通过Diff比较出差异
3、将结果告知Render(渲染器)做渲染
以上的过程是不可中断的,每次获取Virtual Dom都必须整个树遍历,只要当前节点有孩子节点就要一直遍历到结束(该过程也被成为Stack Reconciler)。

同时我们知道在浏览器上,javaScript线程和渲染线程是两个互斥线程,当JavaScript线程占用了大量的时间时,渲染线程就无法工作。就像下图是React团队介绍Fiber用到的一个图,woker就像我们的javaScript线程,他需要长时间离开自己的工作台,无法返回工作。

而人眼能够接受的最合适帧数是60FPS,即任何UI上的刷新变化如果能在16ms内完成,人眼就察觉不出卡顿的效果,这对JavaScipt线程每次任务的执行有很高的时效性要求。
What
从上面的介绍我们可以知道,React v15的版本有两个问题
1、Virtual Dom的构造不可中断
2、React工作的时间必须足够短,能够把主线程交还给渲染线程使用
为了解决上面说的问题,从react v16开始,设计了一种React Fiber架构解决上述问题。Fiber核心其实是对整个调度渲染的重新设计,他有如下3个特点
1、可中断
2、分时操作
3、优先级
不同于Stack Reconciler,Fiber机制下,Dom树被转换成Fiber结构,树的遍历不再是完整遍历,而是由一个一个的work unit组成,每个work unit处理一个Fiber节点,要处理下一个Fiber节点前都会判断下是否有足够时间继续做Dom树处理,没有的话就暂停自己,让主线程执行其他优先级更高操作,从而保证了主线程能随时去处理例如用户点击,动画等更高优先级的事。
How
首先我们看下React里定义的Fiber是一个什么数据结构,源代码里的数据字段比较多,我这里就精简写出跟Fiber树相关的几个字段
// A Fiber is work on a Component that needs to be done or was done. There can
// be more than one per component.
export type Fiber = {|
// The resolved function/class/ associated with this fiber.
type: any,
// 关联的真实element
stateNode: any,
// 父Fiber
return: Fiber | null,
// 第一个孩子Fiber
child: Fiber | null,
// 下一个兄弟Fiber
sibling: Fiber | null,
index: number,
//双缓冲下对应的Fiber节点
alternate: Fiber | null,
|};
每个Element元素对应到一个Fiber节点 ,通过return记录父节点,child记录第一个孩子节点,sibling记录下一个兄弟节点,这样原本的树结构就转换成Fiber树结构。

Fiber架构可以分3层
1、Scheduler(调度器)
它的核心是利用window.requestIdleCallback()这个方法(react团队对这个方法做了改造),它会在浏览器有空闲时间的时候执行对应的任务,这样子来保证浏览器能够有机会去处理更高优先级的事情,不会被react的计算任务卡住。
2、Reconciler(协调器)
协调器的任务就是找出Dom的变化准备好Diff数据,相比以前的树全量比较,v16下的Reconciler把对树的比较拆成一次又一次的performUnitOfWork,每次performUnitOfWork前还得判断下是否有足够的时间(shouldYield),由于dom树已经被转换成Fiber树 ,因此每次中断都是可跟踪的,只需要等到有足够时间就可以从上次中断的位置继续performUnitOfWork。
function workLoopConcurrent() {
// Perform work until Scheduler asks us to yield
while (workInProgress !== null && !shouldYield()) {
workInProgress = performUnitOfWork(workInProgress)
}
}
//shouldYield 最后调用的方法
function shouldYieldToHost() {
const timeElapsed = getCurrentTime() - startTime;
if (timeElapsed < frameInterval) {
// The main thread has only been blocked for a really short amount of time;
// smaller than a single frame. Don't yield yet.
return false;
}
// The main thread has been blocked for a non-negligible amount of time. We
// may want to yield control of the main thread, so the browser can perform
// high priority tasks. The main ones are painting and user input. If there's
// a pending paint or a pending input, then we should yield. But if there's
// neither, then we can yield less often while remaining responsive. We'll
// eventually yield regardless, since there could be a pending paint that
// wasn't accompanied by a call to `requestPaint`, or other main thread tasks
// like network events.
if (enableIsInputPending) {
if (needsPaint) {
// There's a pending paint (signaled by `requestPaint`). Yield now.
return true;
}
if (timeElapsed < continuousInputInterval) {
// We haven't blocked the thread for that long. Only yield if there's a
// pending discrete input (e.g. click). It's OK if there's pending
// continuous input (e.g. mouseover).
if (isInputPending !== null) {
return isInputPending();
}
} else if (timeElapsed < maxInterval) {
// Yield if there's either a pending discrete or continuous input.
if (isInputPending !== null) {
return isInputPending(continuousOptions);
}
} else {
// We've blocked the thread for a long time. Even if there's no pending
// input, there may be some other scheduled work that we don't know about,
// like a network event. Yield now.
return true;
}
}
// `isInputPending` isn't available. Yield now.
return true;
}
当这个Fiber树的节点都完成performUnitOfWork之后,才会继续下一步的Render操作,因此即使频繁中断,我们也不会看到更新一半的Dom树。
3、Render(渲染器)
根据前面两步得到的Diff标记(effectTag,一般有PLACEMENT,UPDATE,DELETION3种值)去做不同的更新操作,这个过程就是不可中断,同步执行的,和v15版本差别不大。
经过上面的3个过程,React的线程操作更像下图,不会大量抢走线程,很容易切回主线程去执行更高优先级的事情。

参考:
Why Fiber-Fiber 解决了什么问题,是怎么解决的
The how and why on React’s usage of linked list in Fiber to walk the component’s tree
Teşekkürler! Parma Safety, İstanbul’da iş güvenliği malzemeleri konusunda sektördeki en güvenilir ve kaliteli ürünleri sunuyor. Web sitenizi gezdiğimde gerçekten ne kadar profesyonel bir hizmet sunduğunuzu fark ettim. Harika bir iş çıkarmışsınız!