在React之前的版本中,进行更新时是连续的,将会阻塞整个线程。如果此时有一些重要的任务,比如用户交互、动画等被阻塞时,体验显然是很差的。而有些更新实际并不用着急完成,比如根据网络请求结果更新的内容,或者发生在画面以外。React16为了解决这一问题,而提出了Fiber的核心架构。
概念
之前的React基于栈(stack),当函数被调用,会有新的栈帧(stack frame)进入栈,栈帧包含了这个函数要执行的工作的信息。执行完,栈帧出栈,当栈为空时才停止工作。
为了解决之前UI渲染的问题,有必要自定义调用栈的行为,从而可以根据意愿中断调用栈并手动操作栈帧。
fiber就是栈的特殊实现,将函数的调用栈帧转为内存中可控的fiber,从而可以决定何时执行这些工作(perform unit of work),或许可以视为将递归转为了循环。
Fiber引入了新的概念incremental rendering(渐进式渲染,将渲染工作分隔,分布到不同的帧)。
incremental rendering: the ability to split rendering work into chunks and spread it out over multiple frames.
整个update的work可以分成不同的工作单元(unit of work),fiber就代表着工作单元。
基本属性 fiber包含着work的对象(what to do)和方式(how to do),其属性就是为这两者服务。
what fiber的工作对象可以由fiber.stateNode
得到。我们知道JSX解析后得到React Element,根据其type可以得到不同的实例,这些实例就是work的对象,而从实例也可以反回来得到fiber。type是string对应HostComponent实例(DOM元素,其_reactInternalInstance
为fiber),type为构造函数对应ClassComponent实例(其_reactInternalFiber
为fiber。需要注意React还会为ReactDOM.render
的容器元素创建单独的HostRoot对象(其current
为fiber)。
instance的相关信息
tag 实例类型,重要:HostRoot 3, ClassComponent 2, HostComponent 5, HostText 6
key 对应实例element的key
type 对应实例element的type
internalContextTag 表示是否默认异步渲染 AsyncUpdates 1
fiber之间的联系,可以反映出其实例的联系
return 父元素
child 第一个子元素
sibling 相邻后一个兄弟元素
how
对于一个实例,最多会有两个对应的fiber(current和workInProgress),两者通过alternate互相指向,即current.alternate === workInProgress,workInProgress.alternate === current。current表示实例当前的状态,当需要更新时,会根据current复制出workInProgress,两者的信息相同,改变都发生在workInProgress上。当更新完成后,workInProgress会变成current。
updateQueue 包含了更新的信息,形式有多种。
对于ClassComponent,是一个setState形成的Update对象组成的链表。
对于HostComponent,是DOM元素新旧属性对比得到的一个数组,描述了该如何更新其属性。比如<div id="i1">
要变成<div id="i2">
,会得到一个数组["id", "i2"]
,表示将id变为i2。
memorizedState 实例最终的state
pendingProps 实例即将接受的props,作用在workInProgress上
memorizedProps 实例最终的props
expirationTime 完成工作的期限
1 2 3 4 5 NoWork = 0 Sync = 1 Never = 2147483647
effectTag 更新造成side effect的类型
React将更新的差异视为side effect,其类型以二进制表示,好处是可以使用位运算,&取交,|取并。
1 2 3 4 5 6 7 8 9 NoEffect Placement Update Deletion fiber.effectTag & Deletion fiber.effectTag |= Placement fiber.effectTag &= ~Update
effect相关属性
Scheduler 既然不使用栈,那各个工作单元的执行过程也不以栈的形式决定。React有自己的调度(scheduler),来决定how和when执行工作单元。以下根据React应用更新的流程来具体分析fiber及scheduler的运用,注意下面的代码为便于理解,相比源码做了一定的简化。
要更新一个React应用的状态,最常用的就是ReactDOM.render和setState,两者的流程大致相同。
找到更新对应的fiber render对应的是HostRoot实例的fiber,是最顶层的fiber,初次渲染时需要新建HostRoot实例及其fiber;而setState对应的则是ClassComponent实例的fiber
计算更新的expirationTime 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 function computeExpirationForFiber (fiber ) { let expirationTime; if (expirationContext !== NoWork) { expirationTime = expirationContext; } else if (isWorking) { if (isCommitting) { expirationTime = Sync; } else { expirationTime = nextRenderExpirationTime; } } else { if (fiber.internalContextTag & AsyncUpdates) { expirationTime = computeAsyncExpiration(); } else { expirationTime = Sync; } } return expirationTime; }
insertUpdateIntoFiber 更新以Update的形式插入fiber的updateQueue,updateQueue的expirationTime保持为其中update里最小的expirationTime,也可以反映出fiber的expirationTime。
scheduleWork 从当前的fiber开始,冒泡到root fiber,更新expirationTime。所以无论更新发生在哪里,最终都是从root开始工作的。
1 2 3 4 5 6 7 8 9 10 11 12 13 while (node !== null ) { if (node.expirationTime === NoWork || node.expirationTime > expirationTime) { node.expirationTime = expirationTime; } if (node['return' ] === null ) { if (node.tag === HostRoot) { var root = node.stateNode; requestWork(root, expirationTime); } } node = node['return' ]; }
requestWork 每当有更新发生,都会请求(request)开始工作,这时根据具体情况决定是否执行工作(performWork)。
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 function requestWork (root, expirationTime ) { if (root.nextScheduledRoot === null ) { root.remainingExpirationTime = expirationTime; if (lastScheduledRoot === null ) { firstScheduledRoot = lastScheduledRoot = root; root.nextScheduledRoot = root; } else { lastScheduledRoot.nextScheduledRoot = root; lastScheduledRoot = root; lastScheduledRoot.nextScheduledRoot = firstScheduledRoot; } } else { var remainingExpirationTime = root.remainingExpirationTime; if (remainingExpirationTime === NoWork || expirationTime < remainingExpirationTime) { root.remainingExpirationTime = expirationTime; } } if (isRendering) { return ; } if (expirationTime === Sync) { performWork(Sync, null ); } else { scheduleCallbackWithExpiration(expirationTime); } }
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 function performWork (minExpirationTime, dl ) { deadline = dl; findHighestPriorityRoot(); while (nextFlushedRoot !== null && nextFlushedExpirationTime !== NoWork && (minExpirationTime === NoWork || nextFlushedExpirationTime <= minExpirationTime) && !deadlineDidExpire) { performWorkOnRoot(nextFlushedRoot, nextFlushedExpirationTime); findHighestPriorityRoot(); } if (nextFlushedExpirationTime !== NoWork) { scheduleCallbackWithExpiration(nextFlushedExpirationTime); } deadline = null ; deadlineDidExpire = false ; }
work分为两个阶段,render和commit
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 function performWorkOnRoot (root, expirationTime ) { isRendering = true let finishedWork = root.finishedWork if (finishedWork !== null ) { root.finishedWork = null root.remainingExpirationTime = commitRoot(finishedWork) } else { finishedWork = renderRoot(root, expirationTime) if (finishedWork !== null ) { if (expirationTime < recalculateCurrentTime()) { root.remainingExpirationTime = commitRoot(finishedWork) } else if (!shouldYield()) { root.remainingExpirationTime = commitRoot(finishedWork) } else { root.finishedWork = finishedWork } } } isRendering = false } function shouldYield ( ) { if (deadline === null ) return false if (deadline.timeRemaining() > 1 ) return false deadlineDidExpire = true return true }
renderRoot 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function renderRoot (root, expirationTime ) { isWorking = true root.isReadyForCommit = false if (root !== nextRoot || expirationTime !== nextRenderExpirationTime || nextUnitOfWork === null ) { nextRoot = root nextRenderExpirationTime = expirationTime nextUnitOfWork = createWorkInProgress(root.current, null , nextRenderExpirationTime) } workLoop(expirationTime) isWorking = false return root.isReadyForCommit ? root.current.alternate : null }
workLoop 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function workLoop (expirationTime ) { if (nextRenderExpirationTime === NoWork || nextRenderExpirationTime > expirationTime) return if (nextRenderExpirationTime < mostRecentCurrentTime) { while (nextUnitOfWork !== null ) { nextUnitOfWork = performUnitOfWork(nextUnitOfWork) } } else { while (nextUnitOfWork !== null && !shouldYield()) { nextUnitOfWork = performUnitOfWork(nextUnitOfWork) } } }
perform分为begin/complete两步。深度优先遍历,先begin当前的workInProgress,然后往下找child来执行下一步work,如果没有的话complete当前work,并找同级的sibling,还没有的话往上回溯return并complete,继续找sibling。
1 2 3 4 5 6 7 8 function performUnitOfWork (workInProgress ) { let current = workInProgress.alternate let next = beginWork(current, workInProgress, nextRenderExpirationTime) if (next === null ) { next = completeUnitOfWork(workInProgress) } return next }
beginWork diff算法所在,根据实例的类型决定reconcile的方法。通过对比来修改fiber树,确定Placement, Deletion, ContentRest等Tag,新建/更新ClassComponent实例, 根据pendingProps和updateQueue来确定workInProgress的memorizedProps/State
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function beginWork (current, workInProgress, renderExpirationTime ) { if (workInProgress.expirationTime === NoWork || workInProgress.expirationTime > renderExpirationTime) return null switch (workInProgress.tag) { case HostRoot: return updateHostRoot(current, workInProgress, renderExpirationTime) case HostComponent: return updateHostComponent(current, workInProgress, renderExpirationTime) case HostText: return updateHostText(current, workInProgress) case ClassComponent: return updateClassComponent(current, workInProgress, renderExpirationTime) } }
completeUnitOfWork 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 function completeUnitOfWork (workInProgress ) { while (true ) { var current = workInProgress.alternate; var next = completeWork(current, workInProgress, nextRenderExpirationTime); var returnFiber = workInProgress['return' ]; var siblingFiber = workInProgress.sibling; resetExpirationTime(workInProgress, nextRenderExpirationTime); if (returnFiber !== null ) { if (returnFiber.firstEffect === null ) { returnFiber.firstEffect = workInProgress.firstEffect; } if (workInProgress.lastEffect !== null ) { if (returnFiber.lastEffect !== null ) { returnFiber.lastEffect.nextEffect = workInProgress.firstEffect; } returnFiber.lastEffect = workInProgress.lastEffect; } var effectTag = workInProgress.effectTag; if (effectTag > PerformedWork) { if (returnFiber.lastEffect !== null ) { returnFiber.lastEffect.nextEffect = workInProgress; } else { returnFiber.firstEffect = workInProgress; } returnFiber.lastEffect = workInProgress; } } if (siblingFiber !== null ) { return siblingFiber; } else if (returnFiber !== null ) { workInProgress = returnFiber; continue ; } else { var root = workInProgress.stateNode; root.isReadyForCommit = true ; return null ; } } return null ; }
completeWork 主要针对HostComponent实例,包括新建DOM元素,根据pendingProps、memorizedProps得到updateQueue,设置effectTag等。
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 function completeWork (current, workInProgress ) { let newProps = workInProgress.pendingProps if (newProps === null ) { newProps = workInProgress.memorizedProps } else { workInProgress.pendingProps = null } let oldProps = current !== null ? current.memorizedProps : null switch (workInProgress.tag) { case ClassComponent: { return null } case HostComponent: { let type = workInProgress.type if (current !== null && workInProgress.stateNode !== null ) { let instance = workInProgress.stateNode let updatePayload = prepareUpdate(instance, type, oldProps, newProps) updateHostComponent(workInProgress, updatePayload) } else { let instance = createInstance(type, newProps, workInProgress) appendAllChildren(instance, workInProgress) finalizeInitialChildren(type, instance, newProps) workInProgress.stateNode = instance } return null } case HostText: { let newText = newProps; if (current !== null && workInProgress.stateNode !== null ) { var oldText = current.memorizedProps updateHostText(workInProgress, oldText, newText) } else { let textInstance = createTextInstance(newText, workInProgress) workInProgress.stateNode = textInstance } return null } case HostRoot: { return null } } }
commitRoot 完成render阶段后,对比已经完成。通过begin和complete工作,需要做的更新保存在root fiber的firstEffect和lastEffect维持的effect list上,现在需要遍历链表把更新反映到实际的DOM视图上。
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 function commitRoot (finishedWork ) { isWorking = true isCommitting = true let root = finishedWork.stateNode root.isReadyForCommit = false let firstEffect if (finishedWork.effectTag > PerformedWork) { if (finishedWork.lastEffect !== null ) { finishedWork.lastEffect.nextEffect = finishedWork firstEffect = finishedWork.firstEffect } else { firstEffect = finishedWork } } else { firstEffect = finishedWork.firstEffect } nextEffect = firstEffect while (nextEffect !== null ) { commitAllHostEffects() } root.current = finishedWork nextEffect = firstEffect while (nextEffect !== null ) { commitAllLifeCycles() } isWorking = false isCommitting = false root.isReadyForCommit = false return root.current.expirationTime }
总结 各个阶段的具体实现(begin/complete/commit)都比较繁琐,这里省略了,无非是switch case具体情况具体分析,这里重点还是放在整个更新的流程。个人认为难点还是在异步的状态管理上,之前的setState已经是异步,如今又提供了异步渲染,如何在保证性能的同时,建立数据状态与视图的映射?所幸React已经在底层实现,我们只需要声明式地构建应用。
参考
React Fiber Architecture
Lin Clark - A Cartoon Intro to Fiber - React Conf 2017