React 源代码分析(3):WorkLoop 工作流程和源代码
前面介绍了 React 的整体架构和 React 当中重要的数据结构 Fiber,以及 Fiber 的源头 element 对象和 FiberRoot 根对象,这一章开始讲解 React 核心工作过程的源码。
在讲解源码的过程中,最常提到的两个全局变量:
-
current
,虚拟 DOM 树中,diff 算法遍历到的旧 Fiber 节点(不同于fiberRoot.current
); -
workInProgress
,将要替换current
的新 Fiber 节点。
假设 React Reconcile 现在正处在一个组件的更新阶段,React 已经通过 useState
的 dispatch,再调用 scheduleUpdateOnFiber
创建了一次更新,更新的过程是这样的:
图中 Fiber1 就是这次更新的组件在 current 树中对应的虚拟DOM,scheduleUpdateOnFiber
方法作用于它。第五步替换发生在 commit
阶段,不包含在 workLoop 中。
第一轮,完成1和2,生成 new Fiber1;第二轮,完成3和4,生成 new Fiber2,如果还有子组件和兄弟组件,则还会有下一轮。React 就是这样将一次对比与生成的过程看成一次 work,然后不断地循环,这就是 workLoop。
当前这轮 work 当中生成的 Fiber 称为 workInProgressFiber
,源码中常常简称 workInProgress
,显然,每一轮它都会指向不同的新 Fiber。
Concurrent 模式引入后 workLoop 有两种,一种是旧的 workLoopSync
,一种是新的 workLoopConcurrent
,唯一区别就是 workLoopConcurrent
可以被 Schedule 暂停。
function workLoopSync() {
while (workInProgress !== null) {
// workInProgress 全局属性每次循环都会被修改
performUnitOfWork(workInProgress);
}
}
...
import { shouldYield } from './Scheduler';
function workLoopConcurrent() {
// Perform work until Scheduler asks us to yield
while (workInProgress !== null && !shouldYield()) {
performUnitOfWork(workInProgress);
}
}
performUnitOfWork
-
performUnitOfWork
首先会执行beginWork
生成当前workInProgress
的所有第一级子节点,并返回第一个子节点,此时第一个子节点会成为下一个workInProgress
;如果没有子节点则返回 null; -
beginWork
返回值不为 null,则一直循环执行beginWork
,直至返回 null; -
beginWork
返回值为 null,则执行completeUnitOfWork
,对当前workInProgress
的所有第一级子节点进行FLAG
类属性向上冒泡操作,并把其return
属性指向workInProgress
。
const current = workInProgress.alternate;
let next;
// 主要作用是为`workInProgress`生成子节点
next = beginWork(current, workInProgress, subtreeRenderLanes);
workInProgress.memoizedProps = workInProgress.pendingProps;
if (next === null) {
// 主要作用是定义 workInProgress 关于子节点的 flag 属性(属性冒泡),
// 并把子节点 return 属性指向 workInProgress
completeUnitOfWork(workInProgress);
} else {
workInProgress = next;
}
- 进入
completeUnitOfWork
的workInProgress
如果没有子节点则返回下一个兄弟节点,此时下一个兄弟节点会成为新的workInProgress
,重新进入beginWork
阶段; - 进入
completeUnitOfWork
的workInProgress
如果没有下一个兄弟节点,则继续对其父节点进行completeUnitOfWork
过程。
let completedWork = workInProgress
do {
// completedWork(workInProgress)在 current 树中对应的节点
const current = completedWork.alternate;
// completedWork(workInProgress)父节点
const returnFiber = completedWork.return;
let next = completeWork(current, completedWork, subtreeRenderLanes);
if (next !== null) {
// 如果 next 存在,则将 workInProgress 指向 next
workInProgress = next;
return;
}
const siblingFiber = completedWork.sibling;
if (siblingFiber !== null) {
// next 为 null,siblingFiber 不为 null,
// 则将 workInProgress 指向 siblingFiber
workInProgress = siblingFiber;
return;
}
// 该 workInProgress 没有兄弟节点,则将 completeWork 指向其父节点
// 并继续这个循环
completedWork = returnFiber;
workInProgress = completedWork;
} while (completedWork !== null);
总结
-
performUnitOfWork
会循环执行beginWork
和completeWork
; -
beginWork
只负责生成子节点,并返回第一个子节点;completeWork
只负责子节点flag
类属性的冒泡; -
Fiber
树的遍历需要依靠completeUnitOfWork
调度beginWork
和completeWork
两个函数; - workLoop 借助
workInProgress
这个变量,根据更新的组件在内存中勾勒出一棵局部的新 Fiber 树。
图解
假设有以下 React Fiber 树,其中每个圆角方框都代表一个 Fiber
节点;接下来将截取其生成过程的一部分来说明 beginWork
和 completeWork
运作模式:
生成过程:椭圆代表当前 workInProgress
所指向的 Fiber
节点;performUnitOfWork
带着 App 进入了 beginWork
阶段,beginWork
阶段创建了 A 和 C,并将 App 的 child
属性指向 A 和 C;A 的 sibling
属性指向 C;
当 performUnitOfWork
第一次执行,beginWork
返回 A 之后,workInProgress
指向 A,因此 workInProgress !== null
,performUnitOfWork
带着 A 进入 beginWork
阶段,beginWork
阶段创建了 B,并把 A 的 child
属性指向 B;
当 performUnitOfWork
第二次执行,beginWork
返回 B 之后,workInProgress
指向 B,因此 workInProgress !== null
,performUnitOfWork
带着 B 进入 beginWork
阶段,beginWork
阶段根据 tag
属性做一些特殊处理,一直到 beginWork
内部 reconcileChildFibers
函数执行时,判断无子节点并返回 null;
此时生成的 Fiber
树结构为:
因为 beginWork
返回 null,此时 workInProgress
仍指向 B,performUnitOfWork
会调用 completeUnitOfWork
函数,completeWork
函数带着 B 节点进入 completeWork
阶段,completeWork
函数多数情况只会冒泡子节点属性并返回 null,因为 B 没有子节点,所以直接返回 null;
completeUnitOfWork
内部检查 B 节点是否有兄弟节点,发现 B 节点没有兄弟节点,因此completeWork
函数带着 A 节点进入 completeWork
阶段,将其子节点部分属性冒泡到 A 节点,并将其所有子节点 return
属性指向 A;
对节点 A completeWork
过程结束后,completeUnitOfWork
查询 A 是否有兄弟节点,发现有兄弟节点 C,因此返回 C;
completeUnitOfWork
返回节点 C,因此 workInProgress
指向 C,C 会跟之前 A 节点一样,经历同样的 beginWork
和 completeWork
过程,并且因为 C 没有兄弟节点,因此下一个进行 completeWork
的是它的父节点 App,App 进入 completeWork
之前生成的 Fiber
树结构为:
App 进入 completeWork
阶段,冒泡子节点 A、C 部分属性,并将 A、C 节点 return
属性指向 App;
推荐阅读
-
React 源代码分析(3):WorkLoop 工作流程和源代码
-
纯干货分享 | 研发效能提升——敏捷需求篇-而敏捷需求是提升效能的方式中不可或缺的模块之一。 云智慧的敏捷教练——Iris Xu近期在公司做了一场分享,主题为「敏捷需求挖掘和组织方法,交付更高业务价值的产品」。Iris具有丰富的团队敏捷转型实施经验,完成了企业多个团队从传统模式到敏捷转型的落地和实施,积淀了很多的经验。 这次分享主要包含以下2个部分: 第一部分是用户影响地图 第二部分是事件驱动的业务分析Event driven business analysis(以下简称EDBA) 用户影响地图,是一种从业务目标到产品需求映射的需求挖掘和组织的方法。 在软件开发过程中可能会遇到一些问题,比如大家使用不同的业务语言、技术语言,造成角色间的沟通阻碍,还会导致一些问题,比如需求误解、需求传递错误等;这会直接导致产品的功能需求和要实现的业务目标不是映射关系。 但在交付期间,研发人员必须要将这些需求实现交付,他们实则并不清楚这些功能需求产生的原因是什么、要解决客户的哪些痛点。研发人员往往只是拿到了解决方案,需要把它实现,但没有和业务侧一起去思考解决方案是否正确,能否真正的帮助客户解决问题。而用户影响地图通常是能够连接业务目标和产品功能的一种手段。 我们在每次迭代里加入的假设,也就是功能需求。首先把它先实现,再逐步去验证我们每一个小目标是否已经实现,再看下一个目标要是什么。那影响地图就是在这个过程中帮我们不断地去梳理目标和功能之间的关系。 我们在软件开发中可能存在的一些问题 针对这些问题,我们如何避免?先简单介绍做敏捷转型的常规思路: 先做团队级的敏捷,首先把产品、开发、测试人员,还有一些更后端的人员比如交互运维的同学放在一起,组成一个特训团队做交付。这个团队要包含交付过程中所涉及的所有角色。 接着业务敏捷要打通整个业务环节和研发侧的一个交付。上图中可以看到在敏捷中需求是分层管理的,第一层是业务需求,在这个层级是以用户目标和业务目标作为输入进行规划,同时需要去考虑客户的诉求。业务人员通过获取到的业务需求,进一步的和团队一起将其分解为产品需求。所以业务需求其实是我们真正去发布和运营的单元,它可以被独立发布到我们的生产环境上。我们的产品需求其实就是产品的具体功能,它是我们集成和测试的对象,也就是我们最终去部署到系统上的一个基本单元。产品需求再到了我们的开发团队,映射到迭代计划会上要把它分解为相应的技术任务,包括我们平时所说的比如一些前端的开发、后端的开发、测试都是相应的技术任务。所以业务敏捷要达到的目标是需要去持续顺畅高质量的交付业务价值。 将这几个点串起来,形成金字塔结构。最上层我们会把业务目标放在整个金字塔的塔尖。这个业务目标是通过用户的目标以及北极星指标确立的。确认业务目标后再去梳理相应的业务流程,最后生产。另外产品需求包含了操作流程和业务规则,具需求交付时间、工程时间以及我们的一些质量标准的要求。 谈到用户影响的地图,在敏捷江湖上其实有一个传说,大家都有一个说法叫做敏捷需求的“任督二脉”。用户影响地图其实就是任脉,在黑客马拉松上用过的用户故事地图其实叫督脉。所以说用户影响地图是在用户故事地图之前,先帮我们去梳理出我们要做哪些东西。当我们真正识别出我们要实现的业务活动之后,用户故事地图才去梳理我们整个的业务工作流,以及每个工作流节点下所要包含的具体功能和用户故事。所以说用户影响地图需要解决的问题,我们包括以下这些: 首先是范围蔓延,我们在整张地图上,功能和对应的业务目标是要去有一个映射的。这就避免了一些在我们比如有很多干系人参与的会议上,那大家都有不同想法些立场,会提出很多需求(正确以及错误的需求)。这个时候我们会依据目标去看这些需求是否真的是会影响我们的目标。 这里提到的错误需求,比如是利益相关的人提出的、客户认为产品应该有的、某个产品经理需求分析师认为可以有的....但是这些功能在用户影响地图中匹配不到对应目标的话,就需要降低优先级或弃掉。另外,通常我们去制定解决方案的时候,会考虑较完美的实现,导致解决方案括很多的功能。这个时候关键目标至关重要,会帮助我们梳理筛选、确定优先级。 看一下用户影响到地图概貌 总共分为一个三层的结构: 第一层why,你的业务目标哪个是最重要的,为什么?涉及到的角色有哪些? 第二层how ,怎样产生影响?影响用户角色什么样的行为? (不需要去列出所有的影响,基于业务目标) 第三层what,最关键的是在梳理需求时不需一次把所有细节想全,这通常团队中经常遇到的问题。 我们用这个例子来看一下 这是一个客服中心的影响地图,业务目标是 3个月内不增加客服人数的前提下能支持1.5倍的用户数。此业务目标设定是符合 smart 原则的,specific非常的具体,miserable 是可以衡量的,action reoriented是面向活动的, real list 也是很实际的。 量化的目标会指引我们接下来的行动,梳理一个业务目标,尽量去量化,比如 :我们通过打造一条什么样的流水线,能够提高整个部署的效率,时间是原来的 1/2 。这样才是一个能量化的有意义的目标。 回到这幅图, how 层级识别出来的内容,客服角色:想要对它施加的影响,把客户引导到论坛上,帮助客户更容易的跟踪问题,更快速的去定位问题。初级用户:方论坛上找到问题。高级用户:在论坛上回答问题。通过我们这些用户角色,进行活动,完成在不增加客户客服人数的前提下支持更多的用户数量。 最后一个层级,才是我们日常接触比较多的真正的功能的特性和需求,比如引导到客户到论坛上,其实这个产品就需要有一个常见问题的论坛的链接。这个层次需要我们团队进一步地在交付,在每个迭代之前做进一步的梳理,细化成相应的用户故事。 这个是云智慧团队中,自己做的影响地图的范例,可以看下整个的层级结构。序号表示优先级。 那我们用户影响地图可以总结为:
-
ATECC508A 芯片开发说明(3):获取 508A 序列号、随机数源代码和 I2C 抓包分析
-
mybatis 3.x 源代码深度分析和最佳实践(最完整的原文)
-
AQS(AbstractQueuedSynchronizer)源代码深入分析(3)--同步队列及获取和释放锁的排他性原则[10,000 字]。
-
金融科技的高效省力秘籍:打造全面连接、全景覆盖、智能化的数字化运营体系" - 当下金融科技运营:挑战与机遇共存的时代解读 在快速发展的数字技术和企业数字化转型的大背景下,中国金融科技产业步入了提质增效的新阶段。面对市场的起伏变革与不确定性,金融机构需积极拥抱创新,灵活运用新技术,确保在竞争激烈的市场环境中稳固立足。 - 面临的双重考验: 1. 技术迭代压力:持续跟进行业内的科技革新,掌握新兴工具和平台,时刻应对瞬息万变的市场需求是金融科技运营的一大挑战。 2. 安全与隐私挑战:伴随着网络安全风险加剧和数据泄漏频发,如何强化信息安全体系、防范攻击、维护客户资金及隐私安全显得尤为重要。同时,伴随金融科技公司崛起,个人隐私权保障愈发关键。 - 喜人的发展空间: 1. 提升运营效益与降低成本:借助数字化技术,实现流程自动化、信息整合以及数据分析等,有效提升工作效能并缩减运营成本。 2. 扩大市场份额与增收途径:利用数字化手段拓宽销售渠道,优化用户体验,吸引更多用户并带动收入增长。 3. 加强客户联系与提升满意度:通过数字化科技运营,企业能更好地与客户互动沟通,增强客户信任感与忠诚度。 - 构建金融科技降本增效的核心驱动力:实施“全感知、全链接、全场景、智能”的科技运营体系升级路径