Fiber 与 Reconciliation
# 目录
调和的代码主要在 react-reconciler 包中。
# Fiber Reconciliation
# 调和(Reconciliation)的概念
对于 Reconciliation 可以从宏观和微观两个层面进行理解,宏观来看,调和就是在应用挂载、更新、卸载的生命周期中,维护调度器、渲染器、更新器各个部分的协调工作,维护应用渲染的更新循环;从微观来看,就是在渲染过程中,调和维护调用组件树中的组件的 render 方法渲染组件、组件的更新和卸载的生命周期。
React 15.X 中 Stack Reconciler 缺乏对渲染任务优先级的管理,渲染任务可能会阻塞线程,导致其他高优先级的任务无法执行,造成页面卡顿。
Fiber Reconciliation 是在 React16 提出的,使渲染任务分段执行,不阻塞主线程,当进程中有更高优先级的任务时就阻塞渲染任务去执行更高优先级的任务。
# fiber 的功能
fiber 实例与组件实例意义对应(但并非是一对一的关系,而是多对一的关系),负责组件实例的渲染更新。fiber 实例之间通过指针组成一棵 Fiber Tree,可以实现更细粒度的渲染控制。
fiber 提供的功能有:
- 可切分,可中断任务;
- 可重用各分阶段任务,且可以设置优先级;
- 可以在父子组件任务间前进后退切换任务;
- render 方法可以返回多元素(即可以返回数组);
- 支持异常边界处理异常;
# 浏览器对优先级控制的支持
这种优先级的控制在浏览器中依赖于这两个 Api:
requestIdleCallback: 在线程空闲时期调度执行低优先级函数;
requestAnimationFrame: 在下一个动画帧调度执行高优先级函数;
2
- requestIdleCallback 执行低优先级的任务,如数据获取。可分为多个 idle callback 将任务细分,具有 deadline,防止执行时间过程阻塞 UI 渲染。
- requestAnimationFrame 执行高优先级的任务,如交互动画。
不支持这种 Api 的浏览器可以用 js 来模拟。
# fiber、Fiber 与组件的关系
- fiber 实例与组件实例相对应,Fiber Tree 对应组件树,Fiber Root 对应组件树根节点。
- Fiber 只应用的调和算法,fiber 负责组件实例的渲染。
# Fiber 详解
# Fiber 的定义
在 react-reconciler 包中 对 Fiber 由如下的定义(dev 环境下的值已去除):
// A Fiber is work on a Component that needs to be done or was done. There can
// be more than one per component.
// Fiber 作用于需要渲染或者已经渲染的组件。一个组件可能有不止一个 Fiber。
export type Fiber = {|
// These first fields are conceptually members of an Instance. This used to
// be split into a separate type and intersected with the other Fiber fields,
// but until Flow fixes its intersection bugs, we've merged them into a
// single type.
// An Instance is shared between all versions of a component. We can easily
// break this out into a separate object to avoid copying so much to the
// alternate versions of the tree. We put this on a single object for now to
// minimize the number of objects created during the initial render.
// 一个 Fiber 实例有组件的各个历史版本所共享。创建一个完整的对象以减少初次渲染时创建对象的数目。
// Tag identifying the type of fiber.
// 区分 Fiber 的类型:0-21
tag: WorkTag,
// Unique identifier of this child.
// fiber 实例的 key 值。
key: null | string,
// The value of element.type which is used to preserve the identity during
// reconciliation of this child.
// 元素类型 element.type
elementType: any,
// The resolved function/class/ associated with this fiber.
// fiber对应的function/class/module类型组件名.
type: any,
// The local state associated with this fiber.
// fiber所在组件树的根组件FiberRoot对象
stateNode: any,
// Conceptual aliases
// parent : Instance -> return The parent happens to be the same as the
// return fiber since we've merged the fiber and instance.
// Remaining fields belong to Fiber
// The Fiber to return to after finishing processing this one.
// This is effectively the parent, but there can be multiple parents (two)
// so this is only the parent of the thing we're currently processing.
// It is conceptually the same as the return address of a stack frame.
// 处理完当前 fiber 之后返回的 fiber,即父级 fiber。
return: Fiber | null,
// Singly Linked List Tree Structure.
// 子 fiber 和父级 fiber。数据结构:单链表树。
child: Fiber | null,
sibling: Fiber | null,
index: number,
// The ref last used to attach this node.
// I'll avoid adding an owner field for prod and model that as functions.
ref: null | (((handle: mixed) => void) & {_stringRef: ?string}) | RefObject,
// Input is the data coming into process this fiber. Arguments. Props.
// 当前 work-i-progress 的组件 props。
pendingProps: any, // This type will be more specific once we overload the tag.
// 缓存之前的组件的 props。
memoizedProps: any, // The props used to create the output.
// A queue of state updates and callbacks.
// 状态更新和回调的队列,用于对组件做更新。
updateQueue: UpdateQueue<any> | null,
// The state used to create the output
memoizedState: any,
// Dependencies (contexts, events) for this fiber, if it has any
// fiber 依赖
dependencies: Dependencies | null,
// Bitfield that describes properties about the fiber and its subtree. E.g.
// the ConcurrentMode flag indicates whether the subtree should be async-by-
// default. When a fiber is created, it inherits the mode of its
// parent. Additional flags can be set at creation time, but after that the
// value should remain unchanged throughout the fiber's lifetime, particularly
// before its child fibers are created.
// 用于描述 fiber 和 fiber tree 的属性的二进制常量。如 NoMode、StrictMode
mode: TypeOfMode,
// Effect
// effect 的状态
effectTag: SideEffectTag,
// Singly linked list fast path to the next fiber with side-effects.
// 以单链表的结构指向下一个 effect
nextEffect: Fiber | null,
// The first and last fiber with side-effect within this subtree. This allows
// us to reuse a slice of the linked list when we reuse the work done within
// this fiber.
// 当前的 pending effect 和 上一个 effect.
firstEffect: Fiber | null,
lastEffect: Fiber | null,
// Represents a time in the future by which this work should be completed.
// Does not include work found in its subtree.
// expirationTime 表示未来这个任务会被完成的时刻。也就是距离任务执行的时间间隔。到期时间。
expirationTime: ExpirationTime,
// This is used to quickly determine if a subtree has no pending changes.
// 子节点的 expirationTime,取 子树到期时间的最小值。用于判断是否子树还有渲染变化。
childExpirationTime: ExpirationTime,
// This is a pooled version of a Fiber. Every fiber that gets updated will
// eventually have a pair. There are cases when we can clean up pairs to save
// memory if we need to.
// 这个Fiber 的版本池,每个更新的 fiber 都会有一个相对的 alternate fiber。
alternate: Fiber | null,
// Time spent rendering this Fiber and its descendants for the current update.
// This tells us how well the tree makes use of sCU for memoization.
// It is reset to 0 each time we render and only updated when we don't bailout.
// This field is only set when the enableProfilerTimer flag is enabled.
actualDuration?: number,
// If the Fiber is currently active in the "render" phase,
// This marks the time at which the work began.
// This field is only set when the enableProfilerTimer flag is enabled.
actualStartTime?: number,
// Duration of the most recent render time for this Fiber.
// This value is not updated when we bailout for memoization purposes.
// This field is only set when the enableProfilerTimer flag is enabled.
selfBaseDuration?: number,
// Sum of base times for all descendants of this Fiber.
// This value bubbles up during the "complete" phase.
// This field is only set when the enableProfilerTimer flag is enabled.
treeBaseDuration?: number,
|};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
需要注意一下几点:
- stateNode 记录了 fiberRoot 对象。
- return 记录了 parent fiber。
- updateQueue 记录当前节点的更新队列,将会在 schedule 中涉及,fiber 负责在 reconciliation 时组件的渲染工作,在后续的 schedule 中会被此处的更新列表进行 update。
- expirationTime 这个尤为重要,他表示渲染任务执行的到时时间,这个值越小,表示距离任务执行的时间间隔越短。fiber 对于渲染任务优先级的管理依赖于此,相对于 stack reconciliation 中对优先级做几种定量的分类相比,这种计算方法更加细粒度、灵活和精确。
- alternate 是 fiber 的版本记录,表示上一次的 fiber。当需要回退时,可以很快的恢复之前的 fiber 状态。
# fiber 的类型
fiber 被定义为 21 种类型。
export const FunctionComponent = 0; // 函数组件
export const ClassComponent = 1; // 类组件
export const IndeterminateComponent = 2; // Before we know whether it is function or class
export const HostRoot = 3; // Root of a host tree. Could be nested inside another node.
export const HostPortal = 4; // A subtree. Could be an entry point to a different renderer.
export const HostComponent = 5;
export const HostText = 6;
export const Fragment = 7;
export const Mode = 8;
export const ContextConsumer = 9;
export const ContextProvider = 10;
export const ForwardRef = 11;
export const Profiler = 12;
export const SuspenseComponent = 13;
export const MemoComponent = 14;
export const SimpleMemoComponent = 15;
export const LazyComponent = 16;
export const IncompleteClassComponent = 17;
export const DehydratedFragment = 18;
export const SuspenseListComponent = 19;
export const FundamentalComponent = 20;
export const ScopeComponent = 21;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# fiber 的创建
fiber 由工厂函数 createFiber 创建。
const createFiber = function(
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode,
): Fiber {
return new FiberNode(tag, pendingProps, key, mode);
};
function FiberNode(
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode,
) {
// Instance:fiber 实例相关
this.tag = tag;
this.key = key;
this.elementType = null;
this.type = null;
this.stateNode = null;
// Fiber:fiber 架构相关
this.return = null;
this.child = null;
this.sibling = null;
this.index = 0;
this.ref = null;
this.pendingProps = pendingProps;
this.memoizedProps = null;
this.updateQueue = null;
this.memoizedState = null;
this.dependencies = null;
this.mode = mode;
// Effects:effect 相关
this.effectTag = NoEffect;
this.nextEffect = null;
this.firstEffect = null;
this.lastEffect = null;
this.expirationTime = NoWork;
this.childExpirationTime = NoWork;
this.alternate = null;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
创建 alternate fiber:
function createWorkInProgress(
current: Fiber, // 当前的 fiber
pendingProps: any, // 当前组件的 props
expirationTime: ExpirationTime, // 到期时间
): Fiber {
let workInProgress = current.alternate;
if (workInProgress === null) {
// We use a double buffering pooling technique because we know that we'll
// only ever need at most two versions of a tree. We pool the "other" unused
// node that we're free to reuse. This is lazily created to avoid allocating
// extra objects for things that are never updated. It also allow us to
// reclaim the extra memory if needed.
// 如果当前的 alternate fiber 不存在,就创建一个新的 fiber,并处理为 alternate fiber。
workInProgress = createFiber(
current.tag,
pendingProps,
current.key,
current.mode,
);
workInProgress.elementType = current.elementType;
workInProgress.type = current.type;
workInProgress.stateNode = current.stateNode;
// double buffering pooling technique: 互相交替模拟版本池。
workInProgress.alternate = current;
// 将workInProgress挂载到current.alternate很好理解。
current.alternate = workInProgress;
} else {
// 如果 alternate fiber 以存在,将他修改为新的 alternate fiber。
// 减少重复创建对象带来的资源开销。
workInProgress.pendingProps = pendingProps;
// We already have an alternate.
// Reset the effect tag.
workInProgress.effectTag = NoEffect;
// The effect list is no longer valid.
workInProgress.nextEffect = null;
workInProgress.firstEffect = null;
workInProgress.lastEffect = null;
}
workInProgress.childExpirationTime = current.childExpirationTime;
workInProgress.expirationTime = current.expirationTime;
workInProgress.child = current.child;
workInProgress.memoizedProps = current.memoizedProps;
workInProgress.memoizedState = current.memoizedState;
workInProgress.updateQueue = current.updateQueue;
// Clone the dependencies object. This is mutated during the render phase, so
// it cannot be shared with the current fiber.
const currentDependencies = current.dependencies;
workInProgress.dependencies =
currentDependencies === null
? null
: {
expirationTime: currentDependencies.expirationTime,
firstContext: currentDependencies.firstContext,
responders: currentDependencies.responders,
};
// These will be overridden during the parent's reconciliation
workInProgress.sibling = current.sibling;
workInProgress.index = current.index;
workInProgress.ref = current.ref;
return workInProgress;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
- 这里如果 fiber 上已经有 alternate 就直接修改 alternate,不必创新创建 alternate fiber,可以大量减少创建对象带来的内存开销。
- workInProgress 和 current 双向 alternate 记录是为了模拟 fiber 版本池的效果,这里就不需要复杂的数据结构来实现了。alternate 给了 fiber 更新更多的机动性。
- workInProgress 代表了当前 fiber 的稳定版本,可以理解为 stable version,current 代表了 fiber 的更新版本,可以理解为 beta version。运行在页面上的永远是 workInProgress,而只有经过了 schedule 和 render,current 才会成为下一个 workInProgress。
下面来看一下 fiber 中的特例 ———— FiberRoot。
# FiberRoot
# FiberRoot 的定义
在 react-reconciler 包中 ReactFiberRoot.js 文件中可以看到 FiberRoot 有如下定义:
export type FiberRoot = {
...BaseFiberRootProperties,
...ProfilingOnlyFiberRootProperties,
...SuspenseCallbackOnlyFiberRootProperties,
};
2
3
4
5
我们主要来看下 BaseFiberRootProperties。
type BaseFiberRootProperties = {|
// The type of root (legacy, batched, concurrent, etc.)
// fiberRoot 的类型
// export type RootTag = 0 | 1 | 2;
// export const LegacyRoot = 0;
// export const BatchedRoot = 1;
// export const ConcurrentRoot = 2;
tag: RootTag,
// Any additional information from the host associated with this root.
// 与根节点相关的容器信息
containerInfo: any,
// Used only by persistent updates.
pendingChildren: any,
// The currently active root fiber. This is the mutable root of the tree.
// 当前容器中激活的 Fiber 对象
current: Fiber,
pingCache:
| WeakMap<Thenable, Set<ExpirationTime>>
| Map<Thenable, Set<ExpirationTime>>
| null,
// 将被 commit 的 fiber 的到期时间
finishedExpirationTime: ExpirationTime,
// A finished work-in-progress HostRoot that's ready to be committed.
// 将被 commit 的 Fiber(HostRoot)
finishedWork: Fiber | null,
// Timeout handle returned by setTimeout. Used to cancel a pending timeout, if
// it's superseded by a new one.
timeoutHandle: TimeoutHandle | NoTimeout,
// Top context object, used by renderSubtreeIntoContainer
// 顶级的 context 对象
context: Object | null,
pendingContext: Object | null,
// Determines if we should attempt to hydrate on the initial mount
// 是否需要在初次渲染时进行hydrate
+hydrate: boolean,
// List of top-level batches. This list indicates whether a commit should be
// deferred. Also contains completion callbacks.
// TODO: Lift this into the renderer
firstBatch: Batch | null,
// Node returned by Scheduler.scheduleCallback
callbackNode: *,
// Expiration of the callback associated with this root
// callback 的超时时间
callbackExpirationTime: ExpirationTime,
// Priority of the callback associated with this root
callbackPriority: ReactPriorityLevel,
// The earliest pending expiration time that exists in the tree
firstPendingTime: ExpirationTime,
// The earliest suspended expiration time that exists in the tree
firstSuspendedTime: ExpirationTime,
// The latest suspended expiration time that exists in the tree
lastSuspendedTime: ExpirationTime,
// The next known expiration time after the suspended range
nextKnownPendingLevel: ExpirationTime,
// The latest time at which a suspended component pinged the root to
// render again
lastPingedTime: ExpirationTime,
lastExpiredTime: ExpirationTime,
|};
// FiberRootNode 工厂函数
function FiberRootNode(containerInfo, tag, hydrate) {
this.tag = tag;
this.current = null;
this.containerInfo = containerInfo;
this.pendingChildren = null;
this.pingCache = null;
this.finishedExpirationTime = NoWork;
this.finishedWork = null;
this.timeoutHandle = noTimeout;
this.context = null;
this.pendingContext = null;
this.hydrate = hydrate;
this.firstBatch = null;
this.callbackNode = null;
this.callbackPriority = NoPriority;
this.firstPendingTime = NoWork;
this.firstSuspendedTime = NoWork;
this.lastSuspendedTime = NoWork;
this.nextKnownPendingLevel = NoWork;
this.lastPingedTime = NoWork;
this.lastExpiredTime = NoWork;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# 创建 FiberRoot
FiberRoot 由函数 createFiberRoot 创建。
function createFiberRoot(
// 容器信息
containerInfo: any,
// FiberRoot 的类型
tag: RootTag,
hydrate: boolean,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
): FiberRoot {
const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);
// 创建 HostFiberRoot,可见 HostFiberRoot 是 挂载到 FiberRoot.current 上面的
const uninitializedFiber = createHostRootFiber(tag);
root.current = uninitializedFiber;
// 将 FiberRoot 挂载到 HostFiberRoot.stateNode 上。
uninitializedFiber.stateNode = root;
return root;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
由此可见:
- double buffering pooling technique:这里运用了双缓冲技术,从 FiberRoot 可以获取到 HostFiberRoot 的 Fiber 信息,同时从 HostFiberRoot 也可以获取到 FiberRoot 的容器信息。这样当 HostFiberRoot 需要回退时,可以及时的从 FiberRoot 恢复信息。
- FiberRoot 可以看做是真个 FiberTree 的一个容器,记载着一些容器信息和即将被 Commit 的 HostFiberRoot 的信息;HostFiberRoot 才是真正的 FiberTree 的根节点。
# HostFiberRoot
HostFiberRoot 本质上是 Fiber,是 FiberTree 的根节点。
创建 HostFiberRoot 代码如下:
function createHostRootFiber(tag: RootTag): Fiber {
let mode;
if (tag === ConcurrentRoot) {
mode = ConcurrentMode | BatchedMode | StrictMode;
} else if (tag === BatchedRoot) {
mode = BatchedMode | StrictMode;
} else {
mode = NoMode;
}
return createFiber(HostRoot, null, null, mode);
}
2
3
4
5
6
7
8
9
10
11
12
- 调用 createFiber 创建 HostFiberRoot,说明 HostFiberRoot 本质上还是 Fiber,只是 Fiber 被标记为了 HostRoot。
# ReactChildFiber
在生成 FiberRoot 和 HostFiberRoot 之后,还需要为子组件生成各自的子 fiber。这部分在 react-reconciler 包中 ReactChildFiber.js 文件中实现。这个过程大致分为两步:
// 调和 ReactChildFiber
export const reconcileChildFibers = ChildReconciler(true);
// 挂载 ReactChildFiber
export const mountChildFibers = ChildReconciler(false);
2
3
4
这部分较为复杂,之后详解。在更新器章节,还会用到这里的内容。