服务粉丝

我们一直在努力
当前位置:首页 > 财经 >

React 设计原理【送书】

日期: 来源:前端食堂收集编辑:

文末送书

大家好,我是童欧巴。喜迎新年,给堂友们搞几波送书福利,在此感谢博文视点大力支持,具体送书规则请移步文末。

React 从 v15 升级到 v16 后重构了整个架构,v16 及以上版本一直沿用新架构,重构的主要原因在于:旧架构无法实现 Time Slice。



01

新旧架构介绍

React15 架构可以分为两部分:

  • Reconciler(协调器)——VDOM 的实现,负责根据自变量变化计算出 UI 变化。

  • Renderer(渲染器)——负责将 UI 变化渲染到宿主环境中。


在 Reconciler 中,mount 的组件会调用 mountComponent,update 的组件会调用updateComponent,这两个方法都会递归更新子组件,更新流程一旦开始,中途无法中断。

基于这个原因,React16 重构了架构。重构后的架构一直沿用至今,可以分为 3 部分:

  • Scheduler(调度器)——调度任务的优先级,高优先级任务优先进入 Reconciler。

  • Reconciler(协调器)——VDOM 的实现,负责根据自变量变化计算出 UI 变化。

  • Renderer(渲染器)——负责将 UI 变化渲染到宿主环境中。


在新架构中,Reconciler 中的更新流程从递归变成了“可中断的循环过程”。每次循环都会调用 shouldYield 判断当前 Time Slice 是否有剩余时间,没有剩余时间则暂停更新流程,将主线程交给渲染流水线,等待下一个宏任务再继续执行,这就是 Time Slice的实现原理:

function workLoopConcurrent() { // 一直执行任务,直到任务执行完或中断while (workInProgress !== null && !shouldYield()) {  performUnitOfWork(workInProgress);  } }

shouldYield 方法如下:

function shouldYield() { // 当前时间是否大于过期时间// 其中 deadline = getCurrentTime() + yieldInterval // yieldInterval 为调度器预设的时间间隔,默认为 5ms return getCurrentTime() >= deadline; }

过期时间 deadline 在任务执行时被更新为“当前时间+时间间隔”,时间间隔默认为5ms,这也是图 2-3 中每个 Time Slice 宏任务的时间长度是 5ms 左右的原因。

当 Scheduler 将调度后的任务交给 Reconciler 后,Reconciler 最终会为 VDOM 元素标记各种副作用 flags,比如:

// 代表插入或移动元素export const Placement = 0b00000000000000000000000010; // 代表更新元素export const Update = 0b00000000000000000000000100; // 代表删除元素export const Deletion = 0b00000000000000000000001000;

Scheduler 与 Reconciler 的工作都在内存中进行。只有当 Reconciler 完成工作后,工作流程才会进入 Renderer。

Renderer 根据“Reconciler 为 VDOM 元素标记的各种 flags”执行对应操作,比如,如上三个 flags 在浏览器宿主环境中对应三种 DOM 操作。

下面的示例1演示了上述三个模块如何配合工作:count 默认值为 0,每次点击按钮执行 count++,UL 中三个 LI 的内容分别为“1、2、3 乘以 count 的结果”。

示例1:

export default () => { const [count, updateCount] = useState(0); return ( <ul><button onClick={() => updateCount(count + 1)}>乘以{count}</button><li>{1 * count}</li><li>{2 * count}</li><li>{3 * count}</li></ul> ) }

对应工作流程如图1所示。

虚线框中的工作流程随时可能由于以下原因被中断:

 有其他更高优先级任务需要先执行;

 当前 Time Slice 没有剩余时间;

 发生错误。

图 1  新 React 架构工作流程示例

由于虚线框内的工作都在内存中进行,不会更新宿主环境 UI,因此即使工作流程反复中断,用户也不会看到“更新不完全的 UI”。



02

主打特性的迭代

随着 React 架构的重构,上层主打特性也随之迭代。按照“主打特性”划分,React大体经历了四个发展时期:

(1)Sync(同步);

(2)Async Mode(异步模式);

(3)Concurrent Mode(并发模式);

(4)Concurrent Feature(并发特性)

其中,旧架构对应同步时期。异步模式、并发模式、并发特性三个时期与新架构相关。本节主要讲解异步模式、并发模式、并发特性的演进过程。

之前曾提到“CPU 瓶颈”与“I/O 瓶颈”,React 并不是同时解决这两个问题的。首先解决的是“CPU 瓶颈”,解决方式是“架构重构”。重构后Reconciler 的工作流程从“同步”变为“异步、可中断”。正因如此,这一时期的 React被称为 Async Mode。

单一更新的工作流程变为“异步、可中断”并不能完全突破“I/O 瓶颈”,解决问题的关键在于“使多个更新的工作流程并发执行”。所以,React 继续迭代为 Concurrent Mode(并发模式)。在 React 中,Concurrent(并发)概念的意义是“使多个更新的工作流程可以并发执行”。

以上便是从 Sync 到 Async Mode 再到 Concurrent Mode 的演进过程。下一节将讲解从 Concurrent Mode 到 Concurrent Feature 的演进过程。



03

渐进升级策略的迭代

从最初的版本到 v18 版本,React 有多少个版本?从架构角度进行概括,所有 React版本一定属于如下四种情况之一。

情况 1:旧架构(v15 及之前版本属于这种情况)。

情况 2:新架构,未开启并发更新,与情况 1 行为一致(v16、v17 默认属于这种情况)。

情况 3:新架构,未开启并发更新,但是启用了一些新功能(比如 AutomaticBatching)。

情况 4:新架构,已开启并发更新。

React 团队希望:使用旧版本的开发者可以逐步升级到新版本,即从情况 1、2、3向情况 4 升级。但是升级过程中存在较大阻力,因为在情况 4 下,React 的一些行为与情况 1、2、3 不同。比如以下三个生命周期函数在情况 4 的 React 下是“不安全的”:

  • componentWillMount

  • componentWillReceiveProps

  • componentWillUpdate

强制升级可能造成代码不兼容。为了使 React 的新旧版本之间实现平滑过渡,React团队采用了“渐进升级”方案。该方案的第一步是规范代码。v16.3 新增了 StrictMode,针对开发者编写的“不符合并发更新规范的代码”给出提示,逐步引导开发者编写规范代码。比如,使用上述“不安全的”生命周期函数时会产生如图2所示的报错信息。

图2 StrictMode 下使用不安全生命周期函数报错

下一步,React 团队允许“不同情况的 React”在同一个页面共存,借此使“情况 4的 React”逐步渗透至原有项目中。具体做法是提供了以下三种开发模式:

  1. Legacy 模式,通过 ReactDOM.render(<App />, rootNode)创建的应用遵循该模式。默认关闭 StrictMode,表现同情况 2。

  2. Blocking模式 通过 ReactDOM.createBlockingRoot(rootNode).render(<App />)创建的应用遵循该模式,作为从 Legacy 向 Concurrent 过渡的中间模式,默认开启StrictMode,表现同情况 3。

  3. Concurrent 模式,通过 ReactDOM.createRoot(rootNode).render(<App />)创建的应用遵循该模式,默认开启 StrictMode,表现同情况 4。


三种开发模式支持特性对比如图3所示

图3 三种开发模式支持特性对比

为了使不同模式的应用可以在同一个页面内工作,需要对一些底层实现进行调整。比如:调整之前,大多数事件会统一冒泡到 HTML 元素,调整后则冒泡到“应用所在根元素”。这些调整工作发生在 v17,所以v17 也被称作“为开启并发更新做铺垫”的“垫脚石”版本。

2021 年 6 月 8 日,v18 工作组成立。在与社区进行大量沟通后,React 团队意识到当前的“渐进升级”策略存在两方面问题。首先,由于模式影响的是整个应用,因此无法在同一个应用中完成渐进升级。举例说明,开发者将应用中 ReactDOM.render 改为ReactDOM.createBlockingRoot,从 Legacy 模式切换到 Blocking 模式,会自动开启StrictMode。此时,整个应用的“并发不兼容警告”都会上报,开发者需要修复整个应用中的不兼容代码。从这个角度看,“渐进升级”的目的并没有达到。

其次,React 团队发现:开发者从新架构中获益,主要是由于使用了并发特性,并发特性指“开启并发更新后才能使用的那些 React 为了解决 CPU 瓶颈、I/O 瓶颈而设计的特性”,比如:

  • useDeferredValue

  • useTransition


所以,React 团队提出新的渐进升级策略——开发者仍可以在默认情况下使用同步更新,在使用并发特性后再开启并发更新。

在 v18 中运行示例2所示代码,由于 updateCount 在 startTransition 的回调函数中执行(使用了并发特性),因此 updateCount 会触发并发更新。如果 updateCount 没有在startTransition 的回调函数中执行,那么 updateCount 将触发默认的同步更新。

示例2:

const App = () => { const [count, updateCount] = useState(0); const [isPending, startTransition] = useTransition(); const onClick = () => { // 使用了并发特性 useTransition  startTransition(() => { // 本次更新是并发更新 updateCount((count) => count + 1);  });  }; return <h3 onClick={onClick}>{count}</h3>; };


读者可以调试在线示例中这两种情况的调用栈火焰图,根据火焰图中观察到的 “是否开启 Time Slice”来区分“是否是并发更新”。

所以,React 在 v18 中不再提供三种开发模式,而是以“是否使用并发特性”作为“是否开启并发更新”的依据。

具体来说,开发者在 v18 中统一使用 ReactDOM.createRoot 创建应用。当不使用并发特性时,表现如情况 3。使用并发特性后,表现如情况 4。



本文节选自卡颂的新书《React设计原理,基于React18,从理念、架构、实现三个层面解构React

这本书存在两条脉络:

  • 抽象层级逐渐降低

  • 实现越来越复杂的模块

对于前者,本书的抽象层级会逐渐从理念到架构,最后到实现,每一层都屏蔽前一层的影响。

这也是为什么ReactDOM.createRoot这个初始化API会放到第六章再讲解 —— 在这个具体API的背后,是他的理念与架构。

对于后者,本书会从0实现与react相关的6个模块,最后我们会一起在React源码内实现一个新的原生Hook

参与送书活动

堂友们,送书活动的参与规则很简单,在本文留言区与大家分享你最近吃过的美食,即可参与抽奖。

抽奖规则:随机抽 2 名堂友,获得本书的实体书,包邮到家。

截止时间:2023 年 1 月 19 日(周四) 中午 12 点,可能延后,以置顶留言为准,置顶留言即为中奖者。

如何兑奖:中奖的堂友我会主动联系你兑奖。如果 24h 内联系不上视为作废。也欢迎堂友们提前扫码添加我的微信 hungryturbo 以防失联。


▼点击阅读原文,了解本书详情~

相关阅读

  • 技术债的前世今生

  • 在1992年Ward Cunningham在博客中提出技术债这个概念后,技术债这个比喻因完美地表达了遗留技术问题的影响,被一直沿用至今,且一直是行业内关注的焦点。如今各大企业为了建立持
  • B端视觉 | 项目里全是表格页该怎么做加分设计?

  • 表格页面是B端项目中最重要的页面类型,所以我们在前面分享了不少关于表格相关的规范和设计思路。案例解析 | 10个表格加分项设计(加了亿点点细节年前最后一篇长干货,B端表格规
  • “老板,别给自己找麻烦了”

  • 我记得有一年「dark mode」非常火,有人喊它「夜间模式」,也有人喊「黑暗模式」,虽然这两者有些许区别,但只要有人提起,就能意识到对方想要表达的是「dark mode」的意思。如今也不
  • 《表单高手课程》第六期简介

  • 优秀的表单决定了产品的用户体验,精通表单的设计决定了你能在互联网行业走多远。每个人都有学习数学的经历,小学的加减乘数,中学的函数方程,大学的概率微积分;表单设计的入门、进
  • OA套利,屌丝靠亚马逊逆袭的新模式?

  • 这两年,有一个与亚马逊平台相关的概念特别火,尤其是在一些互联网中小创业者的圈子里面,非常受到追捧,甚至一些机构还专门为此开了付费的培训课程,一度被包装成为了通过亚马逊平台
  • Market Consensus,首次出现无一格红色区域的情况…

  • 写完了才发现确实是第一次出现无一格红色区域而全屏灰绿的情况顺周期板块涨势受阻跟着短期弱现实回跌,逆周期板块也连续大幅回落(虽然今天周一涨了大涨一波),最终就出现了这个尴
  • 已有两所学校出分!今年改卷较松!

  • 没想到今年杭州师范大学、浙江师范大学抢跑提前出分了!还没有出分的同学我知道你很急,但你先别急,出分也就在这两天啦!从网上同学晒出的成绩来看,今年阅卷比较松,浙江地区公共课的
  • 牛X,再也不敢动了吧!

  • 1、测试设备测评软件别动我手机软件适用安卓端测评设备小米12测评结果无需授权、海量资源2、别动我手机【安卓端】(软件链接在文章底部)作为手机重度依赖症的患者来说,最怕的就

热门文章

  • “复活”半年后 京东拍拍二手杀入公益事业

  • 京东拍拍二手“复活”半年后,杀入公益事业,试图让企业捐的赠品、家庭闲置品变成实实在在的“爱心”。 把“闲置品”变爱心 6月12日,“益心一益·守护梦想每一步”2018年四

最新文章

  • React 设计原理【送书】

  • 文末送书大家好,我是童欧巴。喜迎新年,给堂友们搞几波送书福利,在此感谢博文视点大力支持,具体送书规则请移步文末。React 从 v15 升级到 v16 后重构了整个架构,v16 及以上版本一
  • 普京把话挑明,中俄关系或将迎来质变!

  • 最近,普京的日子可不太好过。不得不承认,演员总统的演技的确非常了得。前段时间,小泽到欧洲转了一圈,凭借三寸不烂之舌,硬是说服了欧洲众多国家继续援乌。目前看,尽管欧洲国家可能