概览
React
在16.8
版本之后, 使用Fiber
提供了任务的优先级,中断可恢复的能力(开启CM
模式). React
通过Scheduler
将高优先级的任务率先扔进Reconciler
, Reconcile
阶段创建了每个节点(其中可能经历复用或者diff
等),生成Fiber
树,并生成对应的dom
树(暂未插入到页面),这个阶段我们称之为render
阶段(渲染阶段)
React Fiber
使用了双缓存机制, 提供了两棵Fiber
树, 当前展示的这棵树我们称之为current
树(本次更新的上一次更新的树), 还有一棵是首次更新或者触发更新后在内存中生成的树, 我们称之为WorkInProgress
树
双缓存
current
树: current
树上的每一个工作单元展示了当前页面的dom
情况, 首屏渲染时, current
树只存在应用程序的根rootFiber
节点
workInProgress
树: workInProgress
树上的每一个工作单元的形成会在内存中执行, 渲染mount
或者update
时的更新逻辑
具体可以参考卡老师写的React揭秘中理念篇的Fiber结构的工作原理,来详细的理解双缓存的概念——什么是双缓存。
渲染阶段
render
阶段开始于performSyncWorkOnRoot
或performConcurrentWorkOnRoot
,取决于当前的模式. 这个函数是React
整个流程,包括render阶段
和commit阶段
上图红框标出三个断点, 第一个断点是render阶段
的核心
function renderRootSync(root, lanes) {
// ......
if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
{
if (isDevToolsPresent) {
var memoizedUpdaters = root.memoizedUpdaters;
if (memoizedUpdaters.size > 0) {
restorePendingUpdaters(root, workInProgressRootRenderLanes);
memoizedUpdaters.clear();
} // At this point, move Fibers that scheduled the upcoming work from the Map to the Set.
// If we bailout on this work, we'll move them back (like above).
// It's important to move them now in case the work spawns more work at the same priority with different updaters.
// That way we can keep the current update and future updates separate.
movePendingFibersToMemoized(root, lanes);
}
}
// 重置调度队列,并从root节点(新的高优先级的节点)开始调度
prepareFreshStack(root, lanes);
}
{
markRenderStarted(lanes);
}
do {
try {
// 尝试循环创建工作单元
workLoopSync();
break;
} catch (thrownValue) {
handleError(root, thrownValue);
}
} while (true);
// .....
return workInProgressRootExitStatus;
} // Th
复制代码
这个方法最重要的两个方法, 一个是prepareFreshStack
, 这个方法是用于重置调度队列,并从root
节点(新的高优先级的节点)开始调度, 这个函数内部调用了createWorkInProgress
, 我们放到下面一会讲
function workLoopSync() {
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
// performConcurrentWorkOnRoot会调用该方法
function workLoopConcurrent() {
//和上面的区别在于shouldYield, 这个代表是否存在剩余时间
while (workInProgress !== null && !shouldYield()) {
performUnitOfWork(workInProgress);
}
}
复制代码
还有一个是workLoopSync
, 循环创建调用performUnitOfWork
, 通过函数名可以看到是循环执行工作单元, 为遍历到的每个Fiber
节点提供beginWork
function performUnitOfWork(unitOfWork) {
var current = unitOfWork.alternate;
setCurrentFiber(unitOfWork);
var next;
if ( (unitOfWork.mode & ProfileMode) !== NoMode) {
startProfilerTimer(unitOfWork);
// 开始beginWork阶段, 主要是创建当前节点的子Fiber节点
next = beginWork$1(current, unitOfWork, subtreeRenderLanes);
stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
} else {
next = beginWork$1(current, unitOfWork, subtreeRenderLanes);
}
resetCurrentFiber();
unitOfWork.memoizedProps = unitOfWork.pendingProps;
if (next === null) {
// 如果子节点为null, 那么直接把当前节点completeWork
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}
ReactCurrentOwner$2.current = null;
}
复制代码
beginWork
的主要作用在于创建当前节点的子Fiber
节点, 该方法为render
阶段递归阶段递阶段的主要方法
beginWork
function beginWork(current, workInProgress, renderLanes) {
var updateLanes = workInProgress.lanes;
{
if (workInProgress._debugNeedsRemount && current !== null) {
// This will restart the begin phase with a new fiber.
return remountFiber(current, workInProgress, createFiberFromTypeAndProps(workInProgress.type, workInProgress.key, workInProgress.pendingProps, workInProgress._debugOwner || null, workInProgress.mode, workInProgress.lanes));
}
}
// 当前的workInProgress树上正在工作的单元对应的alternate对应的Fiber节点
if (current !== null) {
var oldProps = current.memoizedProps;
var newProps = workInProgress.pendingProps;
if (oldProps !== newProps || hasContextChanged() || ( // Force a re-render if the implementation changed due to hot reload:
workInProgress.type !== current.type )) {
// props或者context变化, 有更新的逻辑
didReceiveUpdate = true;
} else if (!includesSomeLane(renderLanes, updateLanes)) {
didReceiveUpdate = false;
// 这里主要是针对不接受更新的ReactElement对象, 进行复用, 针对不同的类型传递一些属性
switch (workInProgress.tag) {
case HostRoot:
// ...
case // ...
}
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
} else {
if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
// 这个解决了context改变的时候suspense没有触发更新的问题
didReceiveUpdate = true;
} else {
didReceiveUpdate = false;
}
}
} else {
didReceiveUpdate = false;
}
workInProgress.lanes = NoLanes;
switch (workInProgress.tag) {
// 根据tag判断当前工作单元的类型, 基于当前类型进入不同的逻辑
case IndeterminateComponent:
// ...
case LazyComponent:
// ...
case FunctionComponent:
{
var _Component = workInProgress.type;
var unresolvedProps = workInProgress.pendingProps;
var resolvedProps = workInProgress.elementType === _Component ? unresolvedProps : resolveDefaultProps(_Component, unresolvedProps);
return updateFunctionComponent(current, workInProgress, _Component, resolvedProps, renderLanes);
}
case ClassComponent:
{
var _Component2 = workInProgress.type;
var _unresolvedProps = workInProgress.pendingProps;
var _resolvedProps = workInProgress.elementType === _Component2 ? _unresolvedProps : resolveDefaultProps(_Component2, _unresolvedProps);
return updateClassComponent(current, workInProgress, _Component2, _resolvedProps, renderLanes);
}
case HostRoot:
return updateHostRoot(current, workInProgress, renderLanes);
case HostComponent:
return updateHostComponent(current, workInProgress, renderLanes);
case xxxx:
// ..........
}
}
复制代码
我们可以看到beginWork
主要做了几件事情
- 根据
current
判断这个工作单元能否复用 - 如果不能复用则更新, 根据当前工作单元的类型决定进入什么逻辑
- 工作单元类型可以在
src/react-reconciler/ReactWorkTags.js
中找到,div
属于HostComponent
beginWork中的更新逻辑(以div举例)
如果当前工作单元是div
, 那么会进入updateHostComponent
的更新逻辑
function updateHostComponent(current, workInProgress, renderLanes) {
pushHostContext(workInProgress);
if (current === null) {
tryToClaimNextHydratableInstance(workInProgress);
}
var type = workInProgress.type;
var nextProps = workInProgress.pendingProps;
var prevProps = current !== null ? current.memoizedProps : null;
var nextChildren = nextProps.children;
var isDirectTextChild = shouldSetTextContent(type, nextProps);
// 文本节点的优化, 当div下只有一个单独的文本节点,那么react不会单独为这个Fiber节点创建子节点
if (isDirectTextChild) {
nextChildren = null;
} else if (prevProps !== null && shouldSetTextContent(type, prevProps)) {
workInProgress.flags |= ContentReset;
}
markRef(current, workInProgress);
// reconcileChildren是diff子节点, 如果current是null, 说明是mount, 否则是update
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
复制代码
针对div
的更新, 主要调用了reconcileChilren
的方法, 以下是reconcileChilren
方法
function reconcileChildren(current, workInProgress, nextChildren, renderLanes) {
if (current === null) {
// mount时
workInProgress.child = mountChildFibers(workInProgress, null, nextChildren, renderLanes);
} else {
// update时
workInProgress.child = reconcileChildFibers(workInProgress, current.child, nextChildren, renderLanes);
}
}
复制代码
reconcileChilren
在mount
时调用了mountChildFibers
, 在update
时调用了reconcileChildFibers
, 这两个方法都是调用的ChildReconciler
, 返回一个节点并赋值给workInProgress.child
, 因此可以验证beginWork
的目的是创建当前节点的子Fiber
节点
var reconcileChildFibers = ChildReconciler(true);
var mountChildFibers = ChildReconciler(false);
复制代码
ChildReconciler
的参数的含义代表的是是否需要追踪副作用, mount
时不需要追踪副作用,原因是我们只需要被插入一次, 如果追踪副作用, 那么每个节点都将被打上effectTag
为Placement
(插入), 这样commit阶段所有节点都会被插入一次, 这种频繁操作dom
的行为显然是消耗性能且没有必要的
function ChildReconciler(shouldTrackSideEffects) {
// ......其他节点的diff算法
// 这里只展示单一节点的diff算法的源码
function reconcileSingleElement(returnFiber, currentFirstChild, element, lanes) {
var key = element.key;
var child = currentFirstChild;
while (child !== null) {
// TODO: If key === null and child.key === null, then this only applies to
// the first item in the list.
if (child.key === key) {
var elementType = element.type;
if (elementType === REACT_FRAGMENT_TYPE) {
if (child.tag === Fragment) {
deleteRemainingChildren(returnFiber, child.sibling);
var existing = useFiber(child, element.props.children);
existing.return = returnFiber;
{
existing._debugSource = element._source;
existing._debugOwner = element._owner;
}
return existing;
}
} else {
if (child.elementType === elementType || ( // Keep this check inline so it only runs on the false path:
isCompatibleFamilyForHotReloading(child, element) ) || // Lazy types should reconcile their resolved type.
// We need to do this after the Hot Reloading check above,
// because hot reloading has different semantics than prod because
// it doesn't resuspend. So we can't let the call below suspend.
typeof elementType === 'object' && elementType !== null && elementType.$$typeof === REACT_LAZY_TYPE && resolveLazy(elementType) === child.type) {
deleteRemainingChildren(returnFiber, child.sibling);
var _existing = useFiber(child, element.props);
_existing.ref = coerceRef(returnFiber, child, element);
_existing.return = returnFiber;
{
_existing._debugSource = element._source;
_existing._debugOwner = element._owner;
}
return _existing;
}
} // Didn't match.
deleteRemainingChildren(returnFiber, child);
break;
} else {
deleteChild(returnFiber, child);
}
child = child.sibling;
}
if (element.type === REACT_FRAGMENT_TYPE) {
var created = createFiberFromFragment(element.props.children, returnFiber.mode, lanes, element.key);
created.return = returnFiber;
return created;
} else {
var _created4 = createFiberFromElement(element, returnFiber.mode, lanes);
_created4.ref = coerceRef(returnFiber, currentFirstChild, element);
_created4.return = returnFiber;
return _created4;
}
}
function reconcileChildFibers(returnFiber, currentFirstChild, newChild, lanes) {
var isUnkeyedTopLevelFragment = typeof newChild === 'object' && newChild !== null && newChild.type === REACT_FRAGMENT_TYPE && newChild.key === null;
if (isUnkeyedTopLevelFragment) {
newChild = newChild.props.children;
} // Handle object types
if (typeof newChild === 'object' && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE:
return placeSingleChild(reconcileSingleElement(returnFiber, currentFirstChild, newChild, lanes));
case REACT_PORTAL_TYPE:
return placeSingleChild(reconcileSinglePortal(returnFiber, currentFirstChild, newChild, lanes));
case REACT_LAZY_TYPE:
{
var payload = newChild._payload;
var init = newChild._init; // TODO: This function is supposed to be non-recursive.
return reconcileChildFibers(returnFiber, currentFirstChild, init(payload), lanes);
}
}
}
// return deleteRemainingChildren(returnFiber, currentFirstChild);
}
return reconcileChildFibers;
}
复制代码
手动执行ChildReconciler
会返回reconcileChildFibers
, 这里巧妙的使用了闭包, 根据newChild
中的$$typeof
去进行不同逻辑.
参数的newChild
可以看到向上追溯看到是当前workInProgress.pendingProps.children
, 也就是当前工作单元props
上的children
reconcileSingleElement
代码已经展示在上面, 是单一节点的diff
算法, 等讲到diff
算法时在详细解读
function placeSingleChild(newFiber) {
// This is simpler for the single child case. We only need to do a
// placement for inserting new children.
if (shouldTrackSideEffects && newFiber.alternate === null) {
newFiber.flags |= Placement;
}
return newFiber;
}
复制代码
placeSingleChild
就是为新节点添加Placement
操作(mount
时不添加), 并将其返回并添加到workInProgress.child
上
以上为beginWork
的完整流程, 目的在于创建当前工作单元的第一个子Fiber
节点
接下来补充一点逻辑, 为了方便调试, 并解决一些个人遇到的疑难困惑点
bailoutOnAlreadyFinishedWork
function bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes) {
if (current !== null) {
workInProgress.dependencies = current.dependencies;
}
{
stopProfilerTimerIfRunning();
}
markSkippedUpdateLanes(workInProgress.lanes); // Check if the children have any pending work.
if (!includesSomeLane(renderLanes, workInProgress.childLanes)) {
{
return null;
}
cloneChildFibers(current, workInProgress);
return workInProgress.child;
}
复制代码
这个函数的作用在于复用Fiber
节点, 函数调用的时机仅仅会发生在update
时, 根据双缓存机制, 当更新时, current
树已经存在对应的Fiber
节点, 在上文beginWork
中有以下这么一段代码, 我们可以看到当current
树不为null
并且新旧Props context
和type
无变化的时候, 会复用节点(即return bailoutOnAlreadyFinishedWork
),bailoutOnAlreadyFinishedWork
调用cloneChildFibers
// 当前的workInProgress树上正在工作的单元对应的alternate对应的Fiber节点
if (current !== null) {
var oldProps = current.memoizedProps;
var newProps = workInProgress.pendingProps;
if (oldProps !== newProps || hasContextChanged() || (
workInProgress.type !== current.type )) {
// props或者context变化, 有更新的逻辑
didReceiveUpdate = true;
} else if (!includesSomeLane(renderLanes, updateLanes)) {
didReceiveUpdate = false;
// 这里主要是针对不接受更新的ReactElement对象, 进行复用, 针对不同的类型传递一些属性
switch (workInProgress.tag) {
case HostRoot:
// ...
case // ...
}
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
} else {
if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
// 这个解决了context改变的时候suspense没有触发更新的问题
didReceiveUpdate = true;
} else {
didReceiveUpdate = false;
}
}
} else {
didReceiveUpdate = false;
}
复制代码
cloneChildFibers
function cloneChildFibers(current, workInProgress) {
if (!(current === null || workInProgress.child === current.child)) {
{
throw Error( "Resuming work not yet implemented." );
}
}
if (workInProgress.child === null) {
return;
}
var currentChild = workInProgress.child;
var newChild = createWorkInProgress(currentChild, currentChild.pendingProps);
workInProgress.child = newChild;
newChild.return = workInProgress;
while (currentChild.sibling !== null) {
currentChild = currentChild.sibling;
newChild = newChild.sibling = createWorkInProgress(currentChild, currentChild.pendingProps);
newChild.return = workInProgress;
}
newChild.sibling = null;
}
复制代码
从名称可以读出, 这个函数的作用就是clone
当前工作单元的子Fiber
节点, 那么复用节点目的就是把current
树上对应的现在Fiber
节点的子Fiber
节点保存到当前工作单元的child
属性下, 这个方法会调用createWorkInProgress
, 所以createWorkInProgress
创建的不是当前工作单元, 而是当前工作单元的子工作单元
createWorkInProgress
function createWorkInProgress(current, pendingProps) {
var workInProgress = current.alternate;
if (workInProgress === null) {
// 如果workInProgress是null, 那么为其创建一个Fiber节点
workInProgress = createFiber(current.tag, pendingProps, current.key, current.mode);
workInProgress.elementType = current.elementType;
workInProgress.type = current.type;
workInProgress.stateNode = current.stateNode;
// ...省略部分赋值
// 连接两课Fiber树
workInProgress.alternate = current;
current.alternate = workInProgress;
} else {
workInProgress.pendingProps = pendingProps; // Needed because Blocks store data on type.
workInProgress.type = current.type; // We already have an alternate.
// Reset the effect tag.
workInProgress.flags = NoFlags; // The effects are no longer valid.
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; // Clone the dependencies object. This is mutated during the render phase, so
// it cannot be shared with the current fiber.
var currentDependencies = current.dependencies;
workInProgress.dependencies = currentDependencies === null ? null : {
lanes: currentDependencies.lanes,
firstContext: currentDependencies.firstContext
}; // These will be overridden during the parent's reconciliation
workInProgress.sibling = current.sibling;
workInProgress.index = current.index;
workInProgress.ref = current.ref;
// ...省略部分代码
return workInProgress;
}
复制代码
createWorkInProgress
从名称可以看出作用在于创建workInProgress
节点, 这个函数的调用时机在cloneChildFibers
或者prepareFreshStack
中. 这个函数首先判断current.alternate
是否为空, 这里解释一下, current.alternate
为空说明当前工作单元的子工作单元是更新后新创建的, 这个时候我们会为新的工作单元创建一个Fiber
节点, 否则我们对子工作单元进行一个赋值操作即可
全流程
这里梳理一下上述所说的全流程