⭐️ 本文首发自 前端修罗场(点击加入),是
一个由 资深开发者 独立运行 的专业技术社区
,我专注Web 技术、Web3、区块链、答疑解惑、面试辅导以及职业发展
。博主创作的 《前端面试复习笔记》(点击订阅),广受好评,已帮助多位同学提升实力、拿到 offer。现在订阅,私聊博主即可获取一次 免费的模拟面试/简历指导服务,帮你评估知识点的掌握程度,获得更全面的学习指导意见!
React 18 于 2022 年 3 月发布。这个版本侧重于性能改进和渲染引擎的更新。同时,React 18 为并发渲染奠定了基础,未来的 React 功能将在此基础上构建。
在本文中,我将简要介绍 React 18,并通过案例解释并发渲染、自动批处理和 transitions
等几个主要概念。
React 18 功能概览
类别 | 功能 |
---|---|
概念 | 并发 |
特性 | 自动批处理,过渡等 |
APIs | createRoot, hydrateRoot, renderToPipeableStream, renderToReadableStream |
Hooks | useId, useTransition, useDeferredValue, useSyncExternalStore, useInsertionEffect |
更新 | 严格模式 |
废弃 | ReactDOM.render, renderToString |
下面,我会更加详细地解释上面的功能与特性。首先,我们先升级到 React 18
升级到 React 18
首先执行如下命令:
npm install react react-dom
然后,在 index.js
中,将 ReactDOM.render
更改为 ReactDOM.createRoot
以创建 root
节点,并使用 root
节点渲染应用程序。
这是 React 17 中的样子:
import ReactDOM from 'react-dom';
import App from 'App';
const container = document.getElementById('app');
ReactDOM.render(<App />, container);
更新后,这是 React 18 中的样子:
import ReactDOM from 'react-dom';
import App from 'App';
const container = document.getElementById('app');
// 创建 root
const root = ReactDOM.createRoot(container);
//渲染
root.render(<App />);
并发
为了理解并发,这里有一个来自官方的例子:
假设我们需要给两个人打电话——Alice 和 Bob。 在非并发设置中,我们一次只能有一个呼叫。 我们会先打电话给 Alice,结束通话后,再打电话给 Bob。
当通话时间很短时这很好,但是如果与 Alice 的通话等待时间很长(例如等待),这可能是会是一个问题。
而,在并发设置中,我们可以打电话给 Alice,一旦我们被搁置,我们就可以打电话给 Bob。
这并不意味着我们同时与两个人交谈。 它只是意味着我们可以同时有两个或多个并发调用,并决定哪个调用更重要。
同样,在具有并发渲染的 React 18 中,React 可以中断、暂停、恢复或放弃渲染
。 这允许 React 快速响应用户交互,即使它处于繁重的渲染任务中。
在 React 18 之前,渲染是一个单一的、不间断的、同步的事务,一旦渲染开始,就不能被中断。
React 18 引入了并发渲染的基础,为一些新功能,如suspense
、流服务渲染和 transitions
,提供了支持。
React 18 新特性
自动批处理
React 18 具有自动批处理功能。 为了理解批处理,让我们参考一个官方的商店购物示例。
假设你正在为晚餐做意大利面。但是你发现你并没有做意大利面所需要的配料,因此你需要去商店里购买。
这时你开始做饭,发现你缺少一种配料,然后你就去商店里买配料,然后回来继续做饭。回来后却又发现你需要另一种配料,接着你又去商店买……然后再回来。这样下去,你自己可能先疯了。
在 React 中,当你调用 setState 时,批处理有助于减少在状态更改时发生的重新渲染次数。 以前,React 在事件处理程序中批量状态更新是这样的:
const handleClick = () => {
setCounter();
setActive();
setValue();
}
// 最后重新渲染一次。
但是,在事件处理程序之外发生的状态更新不会被批处理。 例如,如果有一个Promise
或正在进行 api 调用,则不会批量更新状态。 像这样:
fetch('/network').then( () => {
setCounter(); // 重新渲染 1 次
setActive(); // 重新渲染 2 次
setValue(); // 重新渲染 3 次
});
//一共重新渲染 3次
如你所知,这样做,不是高性能的方式。 React 18 引入了自动批处理,它允许对所有状态更新进行批处理,即使在 Promise、setTimeouts
和事件回调中也是如此。 这显着减少了 React 必须在后台执行的工作。 React 将等待一个微任务完成,然后再重新渲染。
自动批处理在 React 中是开箱即用的,但如果你想退出,你可以使用 flushSync
。
Transitions
Transitions 是React 18 引入的一个全新的
并发特性
。 它允许你将标记更新作为一个 transitions,这会告诉 React 它们可以被中断执行
,并避免回到已经可见内容的 Suspense 降级方案
。
例如,当你在输入时,会发生两件事:先是输入时闪烁的光标,然后是在后台搜索数据。
如果你觉得向用户呈现搜索到的数据并不是紧急的,那么你可以把这项操作标记为 transitions
。这样,React 将知道哪些更新优先。 这使得提升渲染性能更加容易。
使用上,在 React 中,可以使用 startTransition
将更新标记为transition
。 下面是一个 typeahead
组件在使用transitions
标记时的示例:
import { startTransition } from 'react';
// 紧急
setInputValue(input);
// 非紧急: 将内部的任何非紧急状态更新标记为 Transition
startTransition(() => {
setSearchQuery(input);
});
transitions 与 防抖 或 setTimeout 有何不同?
- 与 setTimeout 不同,startTransition 立即执行。
- setTimeout 有保证的延迟,而 startTransition 的延迟取决于设备的速度和其他紧急渲染。
- 与 setTimeout 不同,startTransition 更新可以被中断,并且不会冻结页面。
- 当标记为 startTransition 时,React 可以为你跟踪挂起状态。
Suspense SSR
客户端渲染和服务端渲染
在客户端呈现的应用程序的过程中,会从服务器加载页面的 HTML 以及运行页面所需的所有 JavaScript。
但是,如果 JavaScript 包很大,或者连接速度很慢,那么这个过程可能需要很长时间。
为了优化用户体验并避免用户坐在空白屏幕上,我们可以使用服务器渲染。
服务器渲染是一种技术,可以在服务器上渲染 React 组件的 HTML 输出并从服务器发送 HTML。 这让用户可以在加载 JS 包时以及在应用程序变得交互之前查看一些 UI。
服务器渲染进一步增强了加载页面的用户体验并减少了交互时间。
在 React 18 之前,这部分通常是应用程序的瓶颈,并且会增加渲染组件所需的时间。
一个慢组件可以减慢整个页面的速度。这是因为服务器渲染要么全部,要么什么都没有。你不能告诉 React 推迟加载慢速组件,也不能告诉 React 为其他组件发送 HTML。
React 18 在服务器上增加了对 Suspense
的支持。在 suspense 的帮助下,可以将应用程序的慢速部分包装在 Suspense
组件中,告诉 React 延迟加载慢速组件。这也可以用于指定可以在加载时显示的加载状态
。
在 React 18 中,一个慢速组件不必减慢整个应用程序的渲染速度。使用 Suspense,可以告诉 React 首先发送其他组件的 HTML 以及占位符的 HTML
。然后,当慢速组件准备好并获取其数据时,服务器渲染器将在同一流中弹出其 HTML
。
通过这种方式,用户可以尽早看到页面的骨架,并随着更多的 HTML 到达而逐渐显示更多的内容。
所有这些都发生在页面上加载任何 JS 或 React 之前,这显着改善了用户体验和用户感知的延迟。
严格模式
React 18 中的严格模式将模拟安装、卸载和重新安装
具有先前状态的组件。 这为将来的可重用状态
奠定了基础,React 可以通过在卸载之前使用相同的组件状态重新安装树来立即安装前一个屏幕。
严格模式将确保组件对多次安装和卸载的效果具有弹性。
结尾
总而言之,React 18 为未来的发布奠定了基础,并专注于改善用户体验。