React Fiber调度原理

Fiber是对React核心算法的重构,2年重构的产物就是Fiber reconciler

核心目标:扩大其适用性,包括动画,布局和手势。 分为5个具体目标(后2个算送的):

  • 把可中断的工作拆分成小任务

  • 对正在做的工作调整优先次序、重做、复用上次(做了一半的)成果

  • 在父子任务之间从容切换(yield back and forth),以支持React执行过程中的布局刷新

  • 支持render()返回多个元素

  • 更好地支持error boundary

既然初衷是不希望JS不受控制地长时间执行(想要手动调度),那么,为什么JS长时间执行会影响交互响应、动画?

因为JavaScript在浏览器的主线程上运行,恰好与样式计算、布局以及许多情况下的绘制一起运行。如果JavaScript运行时间过长,就会阻塞这些其他工作,可能导致掉帧。

React希望通过Fiber重构来改变这种不可控的现状,进一步提升交互体验

浅谈React16框架 - Fiber

Fiber的关键特性如下:

  • 增量渲染(把渲染任务拆分成块,匀到多帧)

  • 更新时能够暂停,终止,复用渲染任务

  • 给不同类型的更新赋予优先级

  • 并发方面新的基础能力

Fiber reconciler

reconcile过程分为2个阶段(phase):

  • (可中断)render/reconciliation 通过构造workInProgress tree得出change
  • (不可中断)commit 应用这些DOM change

fiber tree来张图感受一下:
image

为什么我要使用requestIdleCallback?

通知主线程,要求在不忙的时候告诉我,我有几个不太着急的事情要做

跟requestAnimationFrame一样,requestAnimationFrame允许我们正确地安排动画,同时最大限度地去提升到60fps。而requestIdleCallback则会在某一帧结束后的空闲时间或者用户处于不活跃状态时,处理我们的工作。这表明在不获取用户行为条件下,你能执行相关的工作。目前这个新的API在Chrome Canary(M46+)下可用(需要打开chrome://flags/#enable-experimental-web-platform-features 去开启该功能),这样你从今天开始先尝试玩玩。但要记着,这个API是一个实验性的功能,该规范仍在不断变化,所以任何东西都可能随时改变。

靠自己人工的安排不必要的工作是很困难的。比如,要弄清楚一帧剩余的时间,这显然是不可能的,因为当requestAnimationFrame的回调完成后,还要进行样式的计算,布局,渲染以及浏览器内部的工作等等。上面的话貌似还不能说明什么。为了确保用户不以某种方式进行交互,你需要为各种交互行为添加监听事件(scroll、touch、click),即使你并不需要这些功能,只有这样才能绝对确保用户没有进行交互。另一方面,浏览器能够确切地知道在一帧的结束时有多少的可用时间,如果用户正在交互,通过使用requestIdleCallback这个API,允许我们尽可能高效地利用任何的空闲时间。

接下来让我们看看它的更多细节,且让我们知道如果使用它。

模拟:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var requestIdleCallback =
function (cb) {
if (global.requestIdleCallback) return global.requestIdleCallback(cb)
var start = Date.now();
return setTimeout(function () {
cb({
didTimeout: false,
timeRemaining: function () {
return Math.max(0, 50 - (Date.now() - start));
}
});
}, 1);
}

var cancelIdleCallback =
function (id) {
if (global.cancelIdleCallback) return global.cancelIdleCallback(id)
return clearTimeout(id);
}

exports.requestIdleCallback = requestIdleCallback
exports.cancelIdleCallback = cancelIdleCallback

检查requestIdleCallback

目前requestIdleCallback这个API仍处于初期,所以在使用它之前,你应该检查它是否可用。

1
2
3
4
5
if ('requestIdleCallback' in window) {
// Use requestIdleCallback to schedule work.
} else {
// Do what you’d do today.
}

现在,我们假设已经支持该API。

使用requestIdleCallback

调用requestIdleCallback跟调用requestAnimationFrame十分相似,它需要把回调函数作为第一个参数:

1
requestIdleCallback(myNonEssentialWork);

当 myNonEssentialWork 被调用,会返回一个 deadline 对象,这个对象包含一个方法,该方法会返回一个数字表示你的工作还能执行多长时间:

1
2
3
4
function myNonEssentialWork (deadline) {
while (deadline.timeRemaining() > 0)
doWorkIfNeeded();
}

调用 timeRemaining 这个方法能获得最后的剩余时间,当 timeRemaining() 返回0,如果你仍有其他任务需要执行,你便可以执行另外的requestIdleCallback:

1
2
3
4
5
6
7
function myNonEssentialWork (deadline) {
while (deadline.timeRemaining() > 0 && tasks.length > 0)
doWorkIfNeeded();

if (tasks.length > 0)
requestIdleCallback(myNonEssentialWork);
}

确保你的方法已被调用

当事件很多的时候,你会怎么做?你可能会担心你的回调函数永远不被执行。很好,尽管requestIdleCallback跟requestAnimationFrame很像,但它们也有不同,在于requestIdleCallback有一个可选的第二个参数:含有timeout属性的对象。如果设置了timeout这个值,回调函数还没被调用的话,则浏览器必须在设置的这个毫秒数时,去强制调用对应的回调函数。

1
2
// Wait at most two seconds before processing events.
requestIdleCallback(processPendingAnalyticsEvents, { timeout: 2000 });

如果你的回调函数是因为设置的这个timeout而触发的,你会注意到:

  • timeRemaining()会返回0
  • deadline对象的didTimeout属性值是true

如果你发现didTimeout是true,你的代码可能会是这样子的:

1
2
3
4
5
6
7
8
9
10
function myNonEssentialWork (deadline) {

// Use any remaining time, or, if timed out, just run through the tasks.
while ((deadline.timeRemaining() > 0 || deadline.didTimeout) &&
tasks.length > 0)
doWorkIfNeeded();

if (tasks.length > 0)
requestIdleCallback(myNonEssentialWork);
}

因为设置timeout对你用户导致的潜在破坏(这个操作会使你的app变得迟钝且低质量),请小心地设置这个参数。所以,在这,就让浏览器自己去决定什么时候触发回调吧。

使用requestIdleCallback去上报数据

让我们试试用requestIdleCallback去上报数据。在这种情况下,我们可能希望去跟踪一个事件,如“点击导航栏菜单”。然而,因为通常他们是通过动画展现在屏幕上的,我们希望避免立即发送事件到Google Analytics,因此我们将创建一个事件的数组来延迟上报,且在未来的某个时间点会发送出去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var eventsToSend = [];

function onNavOpenClick () {

// Animate the menu.
menu.classList.add('open');

// Store the event for later.
eventsToSend.push(
{
category: 'button',
action: 'click',
label: 'nav',
value: 'open'
});

schedulePendingEvents();
}

现在我们使用requestIdleCallback来处理那些被挂起的事件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function schedulePendingEvents() {

// Only schedule the rIC if one has not already been set.
if (isRequestIdleCallbackScheduled)
return;

isRequestIdleCallbackScheduled = true;

if ('requestIdleCallback' in window) {
// Wait at most two seconds before processing events.
requestIdleCallback(processPendingAnalyticsEvents, { timeout: 2000 });
} else {
processPendingAnalyticsEvents();
}
}

上面代码中,你可以看到我设置了2秒的超时,但取决于你的应用。因为对于上报的这些分析数据,设置一个timeout来确保数据在一个合理的时间范围内被上报,而不是延迟到某个未知的时间点。这样做才是合理且有意义的。

最后我们来写下requestIdleCallback执行的回调方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function processPendingAnalyticsEvents (deadline) {

// Reset the boolean so future rICs can be set.
isRequestIdleCallbackScheduled = false;

// If there is no deadline, just run as long as necessary.
// This will be the case if requestIdleCallback doesn’t exist.
if (typeof deadline === 'undefined')
deadline = { timeRemaining: function () { return Number.MAX_VALUE } };

// Go for as long as there is time remaining and work to do.
while (deadline.timeRemaining() > 0 && eventsToSend.length > 0) {
var evt = eventsToSend.pop();

ga('send', 'event',
evt.category,
evt.action,
evt.label,
evt.value);
}

// Check if there are more events still to send.
if (eventsToSend.length > 0)
schedulePendingEvents();
}

这个例子中,我假设如果不支持requestIdleCallback,则立即上报数据。然而,对于一个在生产环境的应用,最好是用timeout延迟上报来确保不跟任何相互冲突。

使用requestIdleCallback改变dom

requestIdleCallback可以帮助提高性能的另一个场景是,当你需要做一些非必要的dom改动,比如懒加载,滚动页面时候不断在尾部添加元素。让我们看看requestIdleCallback事实上是如何插入一帧里的。

image

从上图也可看出,和 requestAnimationFrame 每一帧必定会执行不同,requestIdleCallback 是捡浏览器空闲来执行任务。

其作用是会在浏览器空闲时期依次调用函数, 这就可以在主事件循环中执行后台或低优先级的任务,而且不会对像动画和用户交互这样延迟触发而且关键的事件产生影响。函数一般会按先进先调用的顺序执行,除非函数在浏览器调用它之前就到了它的超时时间。

对于浏览器,在给定的一帧内因为太忙而没有去执行任何回调这是有可能的,所以你不应该期望在一帧的末尾有空闲的时间去做任何事。这一点就使得requestIdleCallback跟setImmediate不太像,setImmediate是在每一帧里都会执行。

如果在某一帧的末尾,回调函数被触发,它将被安排在当前帧被commit之后,这表示相应的样式已经改动,同时更最重要的,布局已经重新计算。如果我们在这个回调中进行样式的改动,涉及到的布局计算则会被判无效。如果在下一帧中有任何的读取布局相关的操作,例如getBoundingClientRect,clientWidth等等,浏览器会不得不执行一次强制同步布局(Forced Synchronous Layout),这将是一个潜在的性能瓶颈。

另一个不要在回调中触发Dom改动的原因是,Dom改动是不可预期的,正因为如此,我们可以很容易地超过浏览器给出的时间限期。

最佳的实践就是只在requestAnimationFrame的回调中去进行dom的改动,因为浏览器会优化同类型的改动。这表明我们的代码要在requestIdleCallback时使用文档片段,这样就能在下一个requestAnimationFrame回调中把所有改动的dom追加上去。如果你正在使用Virtual DOM这个库,你可以使用requestIdleCallback进行Dom变动,但真正的Dom改动还是在下一个requestAnimationFrame的回调中,而不是requestIdleCallback的回调中。

所以谨记上面说的,下面来看下代码吧:

1
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
function processPendingElements (deadline) {

// If there is no deadline, just run as long as necessary.
if (typeof deadline === 'undefined')
deadline = { timeRemaining: function () { return Number.MAX_VALUE } };

if (!documentFragment)
documentFragment = document.createDocumentFragment();

// Go for as long as there is time remaining and work to do.
while (deadline.timeRemaining() > 0 && elementsToAdd.length > 0) {

// Create the element.
var elToAdd = elementsToAdd.pop();
var el = document.createElement(elToAdd.tag);
el.textContent = elToAdd.content;

// Add it to the fragment.
documentFragment.appendChild(el);

// Don't append to the document immediately, wait for the next
// requestAnimationFrame callback.
scheduleVisualUpdateIfNeeded();
}

// Check if there are more events still to send.
if (elementsToAdd.length > 0)
scheduleElementCreation();
}

在上面,我创建了一个元素,而且使用添加上了textContent这个属性。但这时候还不应该把元素追加到文档流中去。创建完元素添加到文档片段后,scheduleVisualUpdateIfNeeded则被调用,它会创建一个requestAnimationFrame的回调,这时候,我们就应该把文档片段追加到body中去了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function scheduleVisualUpdateIfNeeded() {

if (isVisualUpdateScheduled)
return;

isVisualUpdateScheduled = true;

requestAnimationFrame(appendDocumentFragment);
}

function appendDocumentFragment() {
// Append the fragment and reset.
document.body.appendChild(documentFragment);
documentFragment = null;
}

一切顺利的话,我们则会看到追加dom到文档中时,并没有什么性能的损耗。

模拟React Fiber

1
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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
/*
* @Description: 理解react-fiber运行基本原理库
*/

/**
* @description: react类组件的继承类
* @param {object} props 组件外部传的props参数
*/
class Component {
constructor(props) {
this.props = props
this.state = this.state || {}
}

setState(updater) {
scheduleWork(this, updater)
}

render() {
throw 'should implement `render()` function'
}
}

Component.prototype.isReactComponent = true;

// tag 代表了不同的 JSX 类型
const tag = {
HostComponent: 'host',
ClassComponent: 'class',
HostRoot: 'root',
HostText: 6,
FunctionalComponent: 1,
HOST_COMPONENT: 'dom',
}

const EXPIRATION_TIME = 1 // ms async 逾期时间
let nextUnitOfWork = null
let pendingCommit = null

// 每当 render 和 scheduleWork (setState) 触发时,我们都会往 updateQueue 中 push 一个状态
// 然后,进而调用 requestIdleCallback 进行更新
const updateQueue = []

/**
* @description: React渲染函数,类似于ReactDom.render
* @param {fn} Vnode 要渲染的组件,我们这里拿class组件举例
* @param {DomElement} Container 挂载的dom节点
* @param {fn} callback 回调函数,render完了之后调用
*/
function render(Vnode, Container, callback) {
updateQueue.push({
fromTag: tag.HostRoot,
stateNode: Container,
props: { children: Vnode }
})

requestIdleCallback(performWork) //开始干活
}

/**
* @description: 调用class类的setState时会调用scheduleWork函数
* @param {fn} instance class函数或者无状态函数
* @param {object} partialState 新的state
*/
function scheduleWork(instance, partialState) {
updateQueue.push({
fromTag: tag.ClassComponent,
stateNode: instance,
partialState: partialState
})
requestIdleCallback(performWork) //开始干活
}


/**
* @description: 每次requestIdleCallback调用时执行的函数
* @param {object} deadline 表示是否这一帧渲染还有时间留给react
*/
function performWork(deadline) {
workLoop(deadline)
if (nextUnitOfWork || updateQueue.length > 0) {
requestIdleCallback(performWork) //继续干
}
}

/**
* @description: workLoop 会一次处理 1 个或者多个 Fiber ,具体处理多少个,要看每一帧具体还剩下多少时间,如果一个 Fiber 消耗太多时间,那么就会等到下一帧再处理下一个 Fiber ,如此循环,遍历整个 VDOM 树
* @param {object} deadline 是否这一帧过期
*/
function workLoop(deadline) {
if (!nextUnitOfWork) {
// 一个周期内只创建一次
// 首次render nextUnitOfWork是这样的
// {
// alternate: undefined
// props: {children: {…}}
// stateNode: div#root
// tag: "root"
// }
// children里有个重要的参数是type fn App(props)
nextUnitOfWork = createWorkInProgress(updateQueue)
}
while (nextUnitOfWork && deadline.timeRemaining() > EXPIRATION_TIME) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork)
}

if (pendingCommit) {
//当全局 pendingCommit 变量被负值
commitAllwork(pendingCommit)
}
}

/**
* @description: 首次render会触发, 作用就是构建 workInProgress 树的顶端并赋值给全局变量 nextUnitOfWork
* @param {updateQueue} 更新队列
* @return: 装饰过的fiber节点
*/
function createWorkInProgress(updateQueue) {
const updateTask = updateQueue.shift()
if (!updateTask) return

if (updateTask.partialState) {
// 证明这是一个setState操作
updateTask.stateNode._internalfiber.partialState = updateTask.partialState
}

const rootFiber =
updateTask.fromTag === tag.HostRoot
? updateTask.stateNode._rootContainerFiber
: getRoot(updateTask.stateNode._internalfiber)

return {
tag: tag.HostRoot,
stateNode: updateTask.stateNode,
props: updateTask.props || rootFiber.props,
alternate: rootFiber // 用于链接新旧的 VDOM
}
}

function getRoot(fiber) {
let _fiber = fiber
while (_fiber.return) {
_fiber = _fiber.return
}
return _fiber
}


/**
* @description: 开始遍历所有fiber节点
* @param {object} orkInProgress 当前的fiber节点
* @return: nextChild
*/
function performUnitOfWork(workInProgress) {
const nextChild = beginWork(workInProgress)
if (nextChild) return nextChild

// 没有 nextChild, 我们看看这个节点有没有 sibling
let current = workInProgress
while (current) {
//收集当前节点的effect,然后向上传递
completeWork(current)
if (current.sibling) return current.sibling
//没有 sibling,回到这个节点的父亲,看看有没有sibling
current = current.return
}
}

/**
* @description: 根据fiber的类型,选择不同的更新fiber策略
* @param {object} currentFiber
*/
function beginWork(currentFiber) {
switch (currentFiber.tag) {
case tag.ClassComponent: {
return updateClassComponent(currentFiber)
}
case tag.FunctionalComponent: {
return updateFunctionalComponent(currentFiber)
}
default: {
return updateHostComponent(currentFiber)
}
}
}

/**
* @description: 更新host、文字或者原生DOM节点
* @param {currentFiber} 当前的fiber节点
*/
function updateHostComponent(currentFiber) {
// 当一个 fiber 对应的 stateNode 是原生节点,那么他的 children 就放在 props 里
if (!currentFiber.stateNode) {
if (currentFiber.type === null) {
//代表这是文字节点
currentFiber.stateNode = document.createTextNode(currentFiber.props)
} else {
//代表这是真实原生 DOM 节点
currentFiber.stateNode = document.createElement(currentFiber.type)
}
}
const newChildren = currentFiber.props.children
return reconcileChildrenArray(currentFiber, newChildren)
}

/**
* @description: 更新无状态组件
* @param {currentFiber} 当前的fiber节点
*/
function updateFunctionalComponent(currentFiber) {
let type = currentFiber.type
let props = currentFiber.props
const newChildren = currentFiber.type(props)

return reconcileChildrenArray(currentFiber, newChildren)
}

/**
* @description: 更新class组件
* @param {currentFiber} 当前的fiber节点
*/
function updateClassComponent(currentFiber) {
let instance = currentFiber.stateNode
if (!instance) {
// 如果是 mount 阶段,构建一个 instance
instance = currentFiber.stateNode = createInstance(currentFiber)
}

// 将新的state,props刷给当前的instance
instance.props = currentFiber.props
instance.state = { ...instance.state, ...currentFiber.partialState }

// 清空 partialState
currentFiber.partialState = null
const newChildren = currentFiber.stateNode.render()

// currentFiber 代表老的,newChildren代表新的
// 这个函数会返回孩子队列的第一个
return reconcileChildrenArray(currentFiber, newChildren)
}

/**
* @description: 创建class组件的实例
* @param {fiber} class组件
* @return: class组件的实例
*/
function createInstance(fiber) {
const instance = new fiber.type(fiber.props)
instance._internalfiber = fiber
return instance
}

const PLACEMENT = 1
const DELETION = 2
const UPDATE = 3

function placeChild(currentFiber, newChild) {
const type = newChild.type

if (typeof newChild === 'string' || typeof newChild === 'number') {
// 如果这个节点没有 type ,这个节点就可能是 number 或者 string
return createFiber(tag.HostText, null, newChild, currentFiber, PLACEMENT)
}

if (typeof type === 'string') {
// 原生节点
return createFiber(tag.HOST_COMPONENT, newChild.type, newChild.props, currentFiber, PLACEMENT)
}

if (typeof type === 'function') {
const _tag = type.prototype.isReactComponent ? tag.ClassComponent : tag.FunctionalComponent

return {
type: newChild.type,
tag: _tag,
props: newChild.props,
return: currentFiber,
effectTag: PLACEMENT
}
}
}

/**
* @description: 返回currentFiber节点的子节点fiber
* @param {currentFiber} 当前fiber节点
* @param {newChildren} 子节点(注意此时是虚拟dom)
*/
function reconcileChildrenArray(currentFiber, newChildren) {
// 对比节点,相同的标记更新
// 不同的标记 替换
// 多余的标记删除,并且记录下来
const arrayfiyChildren = Array.isArray(newChildren) ? newChildren : [newChildren]

let index = 0
let oldFiber = currentFiber.alternate ? currentFiber.alternate.child : null
let newFiber = null

while (index < arrayfiyChildren.length || oldFiber !== null) {
const prevFiber = newFiber
const newChild = arrayfiyChildren[index]
const isSameFiber = oldFiber && newChild && newChild.type === oldFiber.type

if (isSameFiber) {
newFiber = {
type: oldFiber.type,
tag: oldFiber.tag,
stateNode: oldFiber.stateNode,
props: newChild.props,
return: currentFiber,
alternate: oldFiber,
partialState: oldFiber.partialState,
effectTag: UPDATE
}
}

if (!isSameFiber && newChild) {
newFiber = placeChild(currentFiber, newChild)
}

if (!isSameFiber && oldFiber) {
// 这个情况的意思是新的节点比旧的节点少
// 这时候,我们要将变更的 effect 放在本节点的 list 里
oldFiber.effectTag = DELETION
currentFiber.effects = currentFiber.effects || []
currentFiber.effects.push(oldFiber)
}

if (oldFiber) {
oldFiber = oldFiber.sibling || null
}

if (index === 0) {
currentFiber.child = newFiber
} else if (prevFiber && newChild) {
// 这里不懂是干嘛的
prevFiber.sibling = newFiber
}

index++
}
return currentFiber.child
}

function createFiber(tag, type, props, currentFiber, effectTag) {
return {
type,
tag,
props,
return: currentFiber,
effectTag,
}
}

function commitAllwork(topFiber) {
topFiber.effects.forEach(f => {
commitWork(f)
})

topFiber.stateNode._rootContainerFiber = topFiber
topFiber.effects = []
nextUnitOfWork = null
pendingCommit = null
}

function completeWork(currentFiber) {
if (currentFiber.tag === tag.ClassComponent) {
// 用于回溯最高点的 root
currentFiber.stateNode._internalfiber = currentFiber
}

if (currentFiber.return) {
const currentEffect = currentFiber.effects || [] //收集当前节点的 effect list
const currentEffectTag = currentFiber.effectTag ? [currentFiber] : []
const parentEffects = currentFiber.return.effects || []
currentFiber.return.effects = parentEffects.concat(currentEffect, currentEffectTag)
} else {
// 到达最顶端了
pendingCommit = currentFiber
}
}


function commitWork(effectFiber) {
if (effectFiber.tag === tag.HostRoot) {
// 代表 root 节点没什么必要操作
return
}

// 拿到parent的原因是,我们要将元素插入的点,插在父亲的下面
let domParentFiber = effectFiber.return
while (domParentFiber.tag === tag.ClassComponent || domParentFiber.tag === tag.FunctionalComponent) {
// 如果是 class 就直接跳过,因为 class 类型的fiber.stateNode 是其本身实例
domParentFiber = domParentFiber.return
}

//拿到父亲的真实 DOM
const domParent = domParentFiber.stateNode
if (effectFiber.effectTag === PLACEMENT) {
if (effectFiber.tag === tag.HostComponent || effectFiber.tag === tag.HOST_COMPONENT || effectFiber.tag === tag.HostText) {
//通过 tag 检查是不是真实的节点
domParent.appendChild(effectFiber.stateNode)
}
// 其他情况
} else if (effectFiber.effectTag == UPDATE) {
// 更新逻辑 只能是没实现
} else if (effectFiber.effectTag == DELETION) {
//删除多余的旧节点
commitDeletion(effectFiber, domParent)
}
}

function commitDeletion(fiber, domParent) {
let node = fiber
while (true) {
if (node.tag == tag.ClassComponent) {
node = node.child
continue
}
domParent.removeChild(node.stateNode)
while (node != fiber && !node.sibling) {
node = node.return
}
if (node == fiber) {
return
}
node = node.sibling
}
}
1
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
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<script src="./babel.min.js"></script>
<title>Document</title>
</head>

<body>
<div id="root"></div>
<script type="text/babel" src="./react-fiber-haixue.js"></script>
<script type="text/babel">
class App extends Component {
state = {
info: true
}
constructor(props) {
super(props)
setTimeout(() => {
this.setState({
info: !this.state.info
})
}, 1000)
}
render() {
return (
<div>
<span>hello</span>
<span>luy</span>
<div>{this.state.info ? 'imasync' : 'iminfo'}</div>
</div>
)
}
}
render(<App />, document.getElementById('root'))
</script>
</body>

</html>

JS中window.requestAnimationFrame()获取浏览器刷新帧率FPS及相关函数rAF()