「原理篇」你真的了解 React18 的并发吗?

前言

前阵子,打磨已久的React18终于正式发布,其中最重要的一个更新就是并发concurrency)。其他的新特性如SuspenseuseTransitionuseDeferredValue 的内部原理都是基于并发的,可想而知在这次更新中并发的重要性。

但是,并发究竟是什么?React团队引入并发又是为了解决哪些问题呢?它到底是如何去解决的呢?前面提到的React18新特性与并发之间又有什么关系呢?

相信大家在看官方文档或者看其他人描述React新特性时,或多或少可能会对以上几个问题产生疑问。因此,本文将通过分享并发更新的整体实现思路,来帮助大家更好地理解React18这次更新的内容。

什么是并发

首先我们来看一下并发的概念:

并发,在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行

举个通俗的例子来讲就是:

  • 你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行。
  • 你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发。
  • 你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行。

并发的关键是具备处理多个任务的能力,但不是在同一时刻处理,而是交替处理多个任务。比如吃饭到一半,开始打电话,打电话到一半发现信号不好挂断了,继续吃饭,又来电话了…但是每次只会处理一个任务。

在了解了并发的概念后,我们现在思考下,在React中并发指的是什么,它有什么作用呢?

React 为什么需要并发

我们都知道,js是单线程语言,同一时间只能执行一件事情。这样就会导致一个问题,如果有一个耗时任务占据了线程,那么后续的执行内容都会被阻塞。比如下面这个例子:

<button id="btn" onclick="handle()">点击按钮</button>

<script> // 用户点击事件回调
  function handle() {
    console.log('click 事件触发 ')
  }

  // 耗时任务,一直占用线程,阻塞了后续的用户行为
  function render() {
    for (let i = 0; i < 10 ** 5; i++) {
      console.log(i)
    }
  }
  window.onload = function () {
    render()
  } </script> 

当我们点击按钮时,由于render函数一直在执行,所以handle回调迟迟没有执行。对于用户来讲,界面是卡死且无法交互的。

如果我们把这个例子中的render函数类比成React更新过程:即setState触发了一次更新,而这次更新耗时非常久,比如200ms。那么在这200ms的时间内界面是卡死的,用户无法进行交互,非常影响用户的使用体验。如下图所示,200ms内浏览器的渲染被阻塞,且用户的click事件回调也被阻塞。

image.png

那我们该如何解决这个问题呢?React18给出的答案就是:并发

我们可以将react更新看作一个任务,click事件看作一个任务。在并发的情况下,react更新到一半的时候,进来了click任务,这个时候先去执行click任务。等click任务执行完成后,接着继续执行剩余的react更新。这样就保证了即使在耗时更新的情况下,用户依旧是可以进行交互的(interactive)。

虽然这个想法看上去非常不错,但是实现起来就有点困难了。比如更新到一半时怎么中断?更新中断了又怎么恢复呢?如果click又触发了react更新不就同时存在了两个更新了吗,它们的状态怎么区分?等等各种问题。

虽然很困难,但React18确实做到了这一点:

Concurrency is not a feature, per se. It’s a new behind-the-scenes mechanism that enables React to prepare multiple versions of your UI at the same time.

正如官网中描述的:并发是一种新的幕后机制,它允许在同一时间里,准备多个版本的UI,即多个版本的更新,也就是前面我们提到的并发。下面我们将逐步了解React是怎么实现并发的。

浏览器的一帧里做了什么?

首先,我们需要了解一个前置知识点——window.requestIdleCallback。它的功能如下:

window.requestIdleCallback() 方法插入一个函数,这个函数将在浏览器空闲时期被调用。

网上有许多文章在聊到React调度(schedule)和时间切片(time slicing)的时候都提到了这个api。那么这个api究竟有什么作用呢?浏览器的空闲时间又是指的什么呢?

带着这个疑问,我们看看浏览器里的一帧发生了什么。我们知道,通常情况下,浏览器的一帧为16.7ms。由于js是单线程,那么它内部的一些事件,比如 click事件,宏任务,微任务,requestAnimatinFramerequestIdleCallback等等都会在浏览器帧里按一定的顺序去执行。具体的执行顺序如下:

image.png

我们可以发现,浏览器一帧里回调的执行顺序为:

  1. 用户事件:最先执行,比如click等事件。
  2. js代码:宏任务和微任务,这段时间里可以执行多个宏任务,但是必须把微任务队列执行完成。宏任务会被浏览器自动调控。比如浏览器如果觉得宏任务执行时间太久,它会将下一个宏任务分配到下一帧中,避免掉帧。
  3. 在渲染前执行 scroll/resize 等事件回调。
  4. 在渲染前执行requestAnimationFrame回调。
  5. 渲染界面:面试中经常提到的浏览器渲染时html、css的计算布局绘制等都是在这里完成。
  6. requestIdleCallback执行回调:如果前面的那些任务执行完成了,一帧还剩余时间,那么会调用该函数。

从上面可以知道,requestIdleCallback表示的是浏览器里每一帧里在确保其他任务完成时,还剩余时间,那么就会执行requestIdleCallback回调。比如其余任务执行了10ms,那么这一帧里就还剩6.7ms的时间,那么就会触发requestIdleCallback的回调。

了解了这个方法后,我们可以做一个假设:如果我们把React的更新(如200ms)拆分成一个个小的更新(如40 个 5ms 的更新),然后每个小更新放到requestIdleCallback中执行。那么就意味着这些小更新会在浏览器每一帧的空闲时间去执行。如果一帧里有多余时间就执行,没有多余时间就推到下一帧继续执行。这样的话,更新一直在继续,并且同时还能确保每一帧里的事件如click,宏任务,微任务,渲染等能够正常执行,也就可以达到用户可交互的目的。

但是,requestIdleCallback的兼容性太差了:

compility.png

因此,React团队决定自己实现一个类似的功能:时间切片(time slicing)。接下来我们看看时间切片是如何实现的。

时间切片

假如React一个更新需要耗时200ms,我们可以将其拆分为405ms的更新(后续会讲到如何拆分),然后每一帧里只花5ms来执行更新。那么,每一帧里不就剩余16.7 - 5 = 11.7ms的时间可以进行用户事件渲染等其他的js操作吗?如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HSjIdy1Z-1652253577077)(https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7fd7cda8979548899e753b7aa30127a9~tplv-k3u1fbpfcp-zoom-in-crop-mark:1956:0:0:0.image?)]

那么这里就有两个问题:

  • 问题1:如何控制每一帧只执行5ms的更新?
  • 问题2:如何控制40
  • 8
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Vue 和 React 是两个非常流行的 JavaScript 框架,它们在设计理念、语法特性、组件化思想、性能、生态等方面有很多不同之处。 1. 设计理念 Vue 的设计理念是“渐进式框架”,强调易用性和灵活性,可以在现有的项目中逐步引入 Vue,也可以作为全新项目的基础框架。Vue 的核心思想是数据驱动,通过数据的变化来自动更新视图,同时支持声明式渲染和组件化开发。 React 的设计理念是“函数式框架”,强调组件化思想和可复用性,通过将 UI 拆分成独立的组件,使得代码更易于维护和扩展。React 的核心思想是虚拟 DOM,通过比较前后两个虚拟 DOM 的差异,最小化 DOM 操作,提高性能。 2. 语法特性 Vue 使用了模板语法,类似于 HTML,可以直接在模板中使用变量和表达式,还支持指令和过滤器等语法特性,使得模板更加简洁易懂。 React 使用了 JSX 语法,将 HTML 和 JavaScript 代码混合在一起,需要将 HTML 标签转换成 React 组件,使得代码更加灵活和可控。 3. 组件化思想 Vue 和 React 都强调组件化思想,将 UI 拆分成独立的组件,每个组件有自己的状态和生命周期,可以方便地组合和复用。 Vue 的组件化开发比较简单,可以通过单文件组件(.vue)的方式,将模板、JS 代码和 CSS 样式放在同一个文件中,使得组件更加独立和封装。 React 的组件化开发需要手动管理状态和生命周期,需要使用类组件或函数组件的方式定义组件,可以使用 JSX 语法或纯 JavaScript 代码来渲染 UI。 4. 性能 Vue 和 React 都采用了虚拟 DOM 技术来提高性能,通过比较前后两个虚拟 DOM 的差异,最小化 DOM 操作,避免了频繁的页面重绘和回流,提高了性能。 Vue 的虚拟 DOM 采用了双向绑定的方式,可以自动更新视图,但是在大型应用中,可能会导致性能问题。 React 的虚拟 DOM 采用了单向数据流的方式,需要手动管理状态和生命周期,但是在大型应用中,可以更好地控制数据流,提高性能。 5. 生态 Vue 和 React 都有非常丰富的生态圈,有大量的第三方库和插件可以使用,可以满足各种不同需求的开发。 Vue 的生态圈相对较小,但是有很多优秀的插件和组件库,如 Vuex、Vue Router、Element UI 等。 React 的生态圈相对较大,有很多强大的库和框架,如 Redux、React Router、Ant Design 等。 总体来说,Vue 和 React 都是非常优秀的 JavaScript 框架,各有优缺点,需要根据具体需求来选择。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值