问答文章1 问答文章501 问答文章1001 问答文章1501 问答文章2001 问答文章2501 问答文章3001 问答文章3501 问答文章4001 问答文章4501 问答文章5001 问答文章5501 问答文章6001 问答文章6501 问答文章7001 问答文章7501 问答文章8001 问答文章8501 问答文章9001 问答文章9501

如何理解 React Fiber 架构

发布网友 发布时间:2022-05-15 09:17

我来回答

2个回答

懂视网 时间:2022-05-15 13:38

本文主要和大家分享React16.2的fiber架构详解,希望能帮助到大家。insertUpdateIntoFiber 会根据fiber的状态创建一个或两个列队对象,对象是长成这样的对象是长成这样的

//by 司徒正美, 加群:370262116 一起研究React与anujs
// https://github.com/RubyLouvre/anu 欢迎加star
function createUpdateQueue(baseState) {//我们现在是丢了一个null做传参
 var queue = {
 baseState: baseState,
 expirationTime: NoWork,//NoWork会被立即执行
 first: null,
 last: null,
 callbackList: null,
 hasForceUpdate: false,
 isInitialized: false
 };

 return queue;
}

scheduleWork是一个奇怪的方法,只是添加一下参数

 function scheduleWork(fiber, expirationTime) {
 return scheduleWorkImpl(fiber, expirationTime, false);
 }

scheduleWorkImpl的最开头有一个recordScheduleUpdate方法,用来记录调度器的执行状态,如注释所示,它现在相当于什么都没有做

function recordScheduleUpdate() {
 if (enableUserTimingAPI) {//全局变量,默认为true
 if (isCommitting) {//全局变量,默认为false, 没有进入分支
 hasScheduledUpdateInCurrentCommit = true;
 }
 //全局变量,默认为null,没有没有进入分支
 if (currentPhase !== null && currentPhase !== 'componentWillMount' && currentPhase !== 'componentWillReceiveProps') {
 hasScheduledUpdateInCurrentPhase = true;
 }
 }
}

scheduleWorkImpl的一些分支非常复杂,我们打一些断点

function computeExpirationForFiber(fiber) {
 var expirationTime = void 0;
 if (expirationContext !== NoWork) {
 // An explicit expiration context was set;
 expirationTime = expirationContext;
 } else if (isWorking) {
 if (isCommitting) {
 // Updates that occur during the commit phase should have sync priority
 // by default.
 expirationTime = Sync;
 } else {
 // Updates during the render phase should expire at the same time as
 // the work that is being rendered.
 expirationTime = nextRenderExpirationTime;
 }
 } else {
 // No explicit expiration context was set, and we're not currently
 // performing work. Calculate a new expiration time.
 if (useSyncScheduling && !(fiber.internalContextTag & AsyncUpdates)) {
 // This is a sync update
 console.log("expirationTime", Sync)
 expirationTime = Sync;//命中这里
 } else {
 // This is an async update
 expirationTime = computeAsyncExpiration();
 }
 }
 return expirationTime;
 }
 function checkRootNeedsClearing(root, fiber, expirationTime) {
 if (!isWorking && root === nextRoot && expirationTime < nextRenderExpirationTime) {
 console.log("checkRootNeedsClearing对nextRoot,nextUnitOfWork,nextRenderExpirationTime进行置空")
 // Restart the root from the top.
 if (nextUnitOfWork !== null) {
 // This is an interruption. (Used for performance tracking.)
 interruptedBy = fiber;
 }
 nextRoot = null;
 nextUnitOfWork = null;
 nextRenderExpirationTime = NoWork;
 }else{
 console.log("checkRootNeedsClearing就是想酱油")
 }
 }

function scheduleWorkImpl(fiber, expirationTime, isErrorRecovery) {
 recordScheduleUpdate();//现在什么也没做

 var node = fiber;
 while (node !== null) {
 // Walk the parent path to the root and update each node's
 // expiration time.
 if (node.expirationTime === NoWork || node.expirationTime > expirationTime) {
 node.expirationTime = expirationTime;//由于默认就是NoWork,因此会被重写 Sync
 }
 if (node.alternate !== null) {//这里进不去
 if (node.alternate.expirationTime === NoWork || node.alternate.expirationTime > expirationTime) {
  node.alternate.expirationTime = expirationTime;
 }
 }
 if (node['return'] === null) {
 if (node.tag === HostRoot) {//进入这里
  var root = node.stateNode;
  checkRootNeedsClearing(root, fiber, expirationTime);
  console.log("requestWork",root, expirationTime)
  requestWork(root, expirationTime);
  checkRootNeedsClearing(root, fiber, expirationTime);
 } else {
  return;
 }
 }
 node = node['return'];
 }
 }

输出如下

requestWork也很难理解,里面太多全局变量,觉得不是前端的人搞的。为了帮助理解,我们继续加日志

//by 司徒正美, 加群:370262116 一起研究React与anujs

 // requestWork is called by the scheduler whenever a root receives an update.
 // It's up to the renderer to call renderRoot at some point in the future.
 /*
只要root收到更新(update对象),requestWork就会被调度程序调用。
渲染器在将来的某个时刻调用renderRoot。
 */
 function requestWork(root, expirationTime) {
 if (nestedUpdateCount > NESTED_UPDATE_LIMIT) {
 invariant_1(false, 'Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.');
 }

 // Add the root to the schedule.
 // Check if this root is already part of the schedule.
 if (root.nextScheduledRoot === null) {
 // This root is not already scheduled. Add it.
 console.log("设置remainingExpirationTime",expirationTime)
 root.remainingExpirationTime = expirationTime;
 if (lastScheduledRoot === null) {
 console.log("设置firstScheduledRoot, lastScheduledRoot")
 firstScheduledRoot = lastScheduledRoot = root;
 root.nextScheduledRoot = root;
 } else {
 lastScheduledRoot.nextScheduledRoot = root;
 lastScheduledRoot = root;
 lastScheduledRoot.nextScheduledRoot = firstScheduledRoot;
 }
 } else {
 // This root is already scheduled, but its priority may have increased.
 var remainingExpirationTime = root.remainingExpirationTime;
 if (remainingExpirationTime === NoWork || expirationTime < remainingExpirationTime) {
 // Update the priority.
 root.remainingExpirationTime = expirationTime;
 }
 }

 if (isRendering) {
 // Prevent reentrancy. Remaining work will be scheduled at the end of
 // the currently rendering batch.
 return;
 }

 if (isBatchingUpdates) {
 // Flush work at the end of the batch.
 if (isUnbatchingUpdates) {
 // ...unless we're inside unbatchedUpdates, in which case we should
 // flush it now.
 nextFlushedRoot = root;
 nextFlushedExpirationTime = Sync;
 console.log("performWorkOnRoot")
 performWorkOnRoot(nextFlushedRoot, nextFlushedExpirationTime);
 }
 return;
 }

 // TODO: Get rid of Sync and use current time?
 if (expirationTime === Sync) {
 console.log("进入performWork")
 performWork(Sync, null);
 } else {
 scheduleCallbackWithExpiration(expirationTime);
 }
 }

从日志输出来看,requestWork只是修改了两个全局变量,然后进入performWork。这三个内部方法起名很有意思。scheduleWork意为打算工作,requestWork意为申请工作,performWork意为努力工作(正式上班)

function performWork(minExpirationTime, dl) {
 deadline = dl;

 // Keep working on roots until there's no more work, or until the we reach
 // the deadline.
 //这里会将root设置为highestPriorityRoot
 findHighestPriorityRoot();

 if (enableUserTimingAPI && deadline !== null) {
 var didExpire = nextFlushedExpirationTime < recalculateCurrentTime();
 console.log(didExpire)
 stopRequestCallbackTimer(didExpire);
 }

 while (nextFlushedRoot !== null 
 && nextFlushedExpirationTime !== NoWork 
 && (minExpirationTime === NoWork || nextFlushedExpirationTime <= minExpirationTime) 
 && !deadlineDidExpire) {
 console.log("performWorkOnRoot")
 performWorkOnRoot(highestPriorityRoot, nextFlushedExpirationTime);
 // Find the next highest priority work.
 findHighestPriorityRoot();
 }

 // We're done flushing work. Either we ran out of time in this callback,
 // or there's no more work left with sufficient priority.

 // If we're inside a callback, set this to false since we just completed it.
 if (deadline !== null) {
 callbackExpirationTime = NoWork;
 callbackID = -1;
 }
 // If there's work left over, schedule a new callback.
 if (nextFlushedExpirationTime !== NoWork) {
 console.log("scheduleCallbackWithExpiration")
 scheduleCallbackWithExpiration(nextFlushedExpirationTime);
 }

 // Clean-up.
 deadline = null;
 deadlineDidExpire = false;
 nestedUpdateCount = 0;

 if (hasUnhandledError) { //如果有没处理的错误则throw
 var _error4 = unhandledError;
 unhandledError = null;
 hasUnhandledError = false;
 throw _error4;
 }
 }

我们终于进入performWorkOnRoot,performWorkOnRoot的作用是区分同步渲染还是异步渲染,expirationTime等于1,因此进入同步。导步肯定为false

// https://github.com/RubyLouvre/anu 欢迎加star

function performWorkOnRoot(root, expirationTime) {

 isRendering = true;

 // Check if this is async work or sync/expired work.
 // TODO: Pass current time as argument to renderRoot, commitRoot
 if (expirationTime <= recalculateCurrentTime()) {
 // Flush sync work.
 
 var finishedWork = root.finishedWork;
 console.log("Flush sync work.", finishedWork)
 if (finishedWork !== null) {
 // This root is already complete. We can commit it.
 root.finishedWork = null;
 console.log("commitRoot")
 root.remainingExpirationTime = commitRoot(finishedWork);
 } else {
 root.finishedWork = null;
 console.log("renderRoot")
 finishedWork = renderRoot(root, expirationTime);
 if (finishedWork !== null) {
  console.log("继续commitRoot")
  // We've completed the root. Commit it.
  root.remainingExpirationTime = commitRoot(finishedWork);
 }
 }
 } else {
 console.log("Flush async work.")
 // Flush async work.
 // ...略
 }

 isRendering = false;
 }

renderRoot也是怒长,React16代码的特点是许多巨型类,巨型方法,有JAVA之遗风。renderRoot只有前面几行是可能处理虚拟DOM(或叫fiber),后面都是错误边界的

function renderRoot(root, expirationTime) {
 
 isWorking = true;

 // We're about to mutate the work-in-progress tree. If the root was pending
 // commit, it no longer is: we'll need to complete it again.
 root.isReadyForCommit = false;

 // Check if we're starting from a fresh stack, or if we're resuming from
 // previously yielded work.
 if (root !== nextRoot || expirationTime !== nextRenderExpirationTime || nextUnitOfWork === null) {
 // Reset the stack and start working from the root.
 resetContextStack();
 nextRoot = root;
 nextRenderExpirationTime = expirationTime;
 //可能是用来工作的代码
 console.log("createWorkInProgress")
 nextUnitOfWork = createWorkInProgress(nextRoot.current, null, expirationTime);
 }
 //可能是用来工作的代码
 console.log("startWorkLoopTimer")
 startWorkLoopTimer(nextUnitOfWork);
 // 处理错误边界
 var didError = false;
 var error = null;
 invokeGuardedCallback$1(null, workLoop, null, expirationTime);
 // An error was thrown during the render phase.
 while (didError) {
 console.log("componentDidCatch的相关实现")
 if (didFatal) {
 // This was a fatal error. Don't attempt to recover from it.
 firstUncaughtError = error;
 break;
 }

 var failedWork = nextUnitOfWork;
 if (failedWork === null) {
 // An error was thrown but there's no current unit of work. This can
 // happen during the commit phase if there's a bug in the renderer.
 didFatal = true;
 continue;
 }

 // 处理错误边界
 var boundary = captureError(failedWork, error);
 !(boundary !== null) ? invariant_1(false, 'Should have found an error boundary. This error is likely caused by a bug in React. Please file an issue.') : void 0;

 if (didFatal) {
 // The error we just captured was a fatal error. This happens
 // when the error propagates to the root more than once.
 continue;
 }
 // 处理错误边界
 didError = false;
 error = null;
 // We're finished working. Exit the error loop.
 break;
 }
 // 处理错误边界
 var uncaughtError = firstUncaughtError;

 // We're done performing work. Time to clean up.
 stopWorkLoopTimer(interruptedBy);
 interruptedBy = null;
 isWorking = false;
 didFatal = false;
 firstUncaughtError = null;
 // 处理错误边界
 if (uncaughtError !== null) {
 onUncaughtError(uncaughtError);
 }

 return root.isReadyForCommit ? root.current.alternate : null;
 }

 function resetContextStack() {
 // Reset the stack
 reset$1();
 // Reset the cursors
 resetContext();
 resetHostContainer();
 }

function reset$1() {
 console.log("reset",index)
 while (index > -1) {
 valueStack[index] = null;

 {
 fiberStack[index] = null;
 }

 index--;
 }
}

function resetContext() {
 consoel.log("resetContext")
 previousContext = emptyObject_1;
 contextStackCursor.current = emptyObject_1;
 didPerformWorkStackCursor.current = false;
}

 function resetHostContainer() {
 console.log("resetHostContainer",contextStackCursor, rootInstanceStackCursor, NO_CONTEXT )
 contextStackCursor.current = NO_CONTEXT;
 rootInstanceStackCursor.current = NO_CONTEXT;
 }

createWorkInProgress就是将根组件的fiber对象再复制一份,变成其alternate属性。因此 将虚拟DOM转换为真实DOM的重任就交给invokeGuardedCallback

var invokeGuardedCallback = function (name, func, context, a, b, c, d, e, f) {
 ReactErrorUtils._hasCaughtError = false;
 ReactErrorUtils._caughtError = null;
 var funcArgs = Array.prototype.slice.call(arguments, 3);
 try {
 func.apply(context, funcArgs);
 } catch (error) {
 ReactErrorUtils._caughtError = error;
 ReactErrorUtils._hasCaughtError = true;
 }
//这下面还有怒长(100-150L )的关于错误边界的处理,略过
};

func为workLoop

//by 司徒正美, 加群:370262116 一起研究React与anujs

 function workLoop(expirationTime) {
 if (capturedErrors !== null) {
 // If there are unhandled errors, switch to the slow work loop.
 // TODO: How to avoid this check in the fast path? Maybe the renderer
 // could keep track of which roots have unhandled errors and call a
 // forked version of renderRoot.
 slowWorkLoopThatChecksForFailedWork(expirationTime);
 return;
 }
 if (nextRenderExpirationTime === NoWork || nextRenderExpirationTime > expirationTime) {
 return;
 }

 if (nextRenderExpirationTime <= mostRecentCurrentTime) {
 // Flush all expired work.
 while (nextUnitOfWork !== null) {
 console.log("performUnitOfWork",nextUnitOfWork)
 nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
 }
 } else {
 // Flush asynchronous work until the deadline runs out of time.
 while (nextUnitOfWork !== null && !shouldYield()) {
 nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
 }
 }
 }


我们终于看到工作的代码了。 这个nextUnitOfWork 是renderRoot生成的
performUnitOfWork与beginWork的代码,里面会根据fiber的tag进入各种操作

//by 司徒正美, 加群:370262116 一起研究React与anujs
// https://github.com/RubyLouvre/anu 欢迎加star

function performUnitOfWork(workInProgress) {
 // The current, flushed, state of this fiber is the alternate.
 // Ideally nothing should rely on this, but relying on it here
 // means that we don't need an additional field on the work in
 // progress.
 var current = workInProgress.alternate;

 // See if beginning this work spawns more work.
 startWorkTimer(workInProgress);
 {
 ReactDebugCurrentFiber.setCurrentFiber(workInProgress);
 }
 console.log("beginWork")
 var next = beginWork(current, workInProgress, nextRenderExpirationTime);
 {
 ReactDebugCurrentFiber.resetCurrentFiber();
 }
 if (true && ReactFiberInstrumentation_1.debugTool) {
 ReactFiberInstrumentation_1.debugTool.onBeginWork(workInProgress);
 }

 if (next === null) {
 console.log("next")
 // If this doesn't spawn new work, complete the current work.
 next = completeUnitOfWork(workInProgress);
 }

 ReactCurrentOwner.current = null;

 return next;
 }
function beginWork(current, workInProgress, renderExpirationTime) {
 if (workInProgress.expirationTime === NoWork || workInProgress.expirationTime > renderExpirationTime) {
 return bailoutOnLowPriority(current, workInProgress);
 }

 switch (workInProgress.tag) {
 case IndeterminateComponent:
 return mountIndeterminateComponent(current, workInProgress, renderExpirationTime);
 case FunctionalComponent:
 return updateFunctionalComponent(current, workInProgress);
 case ClassComponent:
 return updateClassComponent(current, workInProgress, renderExpirationTime);
 case HostRoot:
 return updateHostRoot(current, workInProgress, renderExpirationTime);
 case HostComponent:
 return updateHostComponent(current, workInProgress, renderExpirationTime);
 case HostText:
 return updateHostText(current, workInProgress);
 case CallHandlerPhase:
 // This is a restart. Reset the tag to the initial phase.
 workInProgress.tag = CallComponent;
 // Intentionally fall through since this is now the same.
 case CallComponent:
 return updateCallComponent(current, workInProgress, renderExpirationTime);
 case ReturnComponent:
 // A return component is just a placeholder, we can just run through the
 // next one immediately.
 return null;
 case HostPortal:
 return updatePortalComponent(current, workInProgress, renderExpirationTime);
 case Fragment:
 return updateFragment(current, workInProgress);
 default:
 invariant_1(false, 'Unknown unit of work tag. This error is likely caused by a bug in React. Please file an issue.');
 }
 }

我们再调查一下workInProgress.tag是什么

https://github.com/facebook/r...

这里有全部fiber节点的类型描述,我们创建一个对象

// https://github.com/RubyLouvre/anu 欢迎加star

var mapBeginWork = {
 3: "HostRoot 根组件",
 0: "IndeterminateComponent 只知道type为函数",
 2: "ClassComponent 普通类组件" ,
 5: "HostComponent 元素节点",
 6: "HostText 文本节点"
 }
 function beginWork(current, workInProgress, renderExpirationTime) {
 if (workInProgress.expirationTime === NoWork || workInProgress.expirationTime > renderExpirationTime) {
 return bailoutOnLowPriority(current, workInProgress);
 }
 console.log(workInProgress.tag, mapBeginWork[workInProgress.tag])
 switch (workInProgress.tag) {
 //略
 }
}

热心网友 时间:2022-05-15 10:46

React的UI解决方案是,View = F(Data),
页面中的所有相关的React Components共同组成了F,Components之间是互相调用的关系。
页面复杂的话,这个调用栈会很深,导致UI变卡。
(一旦执行,我们就不能干扰它。。
声明声明:本网页内容为用户发布,旨在传播知识,不代表本网认同其观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。E-MAIL:11247931@qq.com
2023党课ppt+讲稿配套课件——全面从严治党 继续推进新时代党的建设新... 廉政建设专题党课:力转工作作风、严守政治底线,以忠诚担当的干劲加强... 大一新生证件照要求 新生报名大学照相要照几寸的 人人网是什么网啦 人人网的主要功能是什么? ...OH为主要原料合成六元环状有机物C和高分子化合物D 酸菜不酸是什么原因 十堰公元海二期是什么装修? 十堰公元海二期售楼服务热线是多少? PB塑料管采购的最大管径是多少 使用混凝土多孔砖会出现什么弊端? 想开个果蔬专卖店请高手取个店名字 我们家要在小区里开一家蔬菜水果店,请各位帮忙起个名字:)谢谢了,呵呵 中国影视院校排名 乌克兰有哪些戏剧大学 电影专业,伦敦的市政厅音乐及戏剧学院在英国排名第几? 材料预算价格 戏剧影视文学哪个学校比较好呢? 戏剧学院有那些? 放羊的星星给夏之星配音的是谁? 网红餐饮店4个特色,普通餐饮如何借鉴 火锅店怎么找网红推广 mc神话小天 毕业季歌词 谁有看过阮经天配音的《冰河世纪3》,麻烦告诉我一下在哪能看到?谢谢!! 日本有很多大牌声优,如钉宫理惠、平野绫、神谷浩史等。我的问题是中国有哪些大牌声优?配音过哪些动漫? 问下《神话》里面小天的来电铃声是什么歌啊? 芒果新树果与老树果怎么区分? 火力少年王5查小天是谁的配音 人参果和芒果的甜度对比 酒店市场传媒助理工作职责和内容 重庆那里可以租得到几百公斤重的挖机 云南有哪些挖掘机租赁公司 坐标成都,想租赁挖掘机,联系方式是多少? 附近有没有小型挖掘机出租 湖北省仙桃市哪有挖机出租工程队? 临沧市孟疋挖掘机出租电话? 付近那里有小型挖掘机出租? 想知道: 福州市长乐垃圾填埋场在哪? 想租台挖掘机干活,到哪里租?怎么租?一般租金多少钱? wear可以做动名词吗 哪里租赁水陆挖掘机 wear是名词动词还是形容词 CK手表怎么样,质量怎么样,求懂的人进来告诉 wearing和worn有什么区别 in和wear的区别是什么,怎样更容易辨别? 长乐漳港街道卫生环境垃圾要找哪个部门投诉 ck手表假货多吗 惠来县有长臂挖掘机出租吗 【英语求助】The girl ___(wear) red is good at dancing and singing.填什么?为什么?