了解 react 18 的新特性对于开发人员来说至关重要,因为这些更新可能会显著改变库的功能和使用方式。使用最新版本的库可以确保获得最佳性能和最新的功能。
本文将探讨 React 18 的定义、React 17 的问题、React 18 引入的新特性以及为什么你应该升级到最新版本。
- React 18 是什么?
在讨论 React 18 的新功能之前,让我们先了解什么是 React 18。任何 18.0.0 以上但不包括 19.0.0 的 React 库的稳定版本都被称为 React 18。
React 18 引入了并发渲染,这是一个重要的新特性。React 一直专注于 DOM 渲染,并为开发人员提供了控制和跟踪组件生命周期的工具。有了这些新功能,React 18 现在能够根据客户端设备的性能调整渲染过程。
- 升级到 React 18
React 社区提供了多种安装选项。要在应用程序中安装 React 18,可以在 HTML 脚本标记中使用 CDN URL 作为源(
src)。
通过在工作目录的终端中执行以下命令,你可以使用 NPM 或 Yarn 升级或安装 React 18,用于单页面和绑定应用程序。NPM:
npm install react react-dom
Yarn:
yarn add react react-dom
上面的命令将自动检测并安装或升级开发环境中最新的 React 和 React DOM 版本。
- React 17 的问题
React 社区已经注意到库中存在一些需要改进的问题。如果 React 17 功能完美,就不需要发布 React 18 和更高版本了。
根据 React 18.0.0 的更新日志,React 17 或更早版本的以下问题得到了解决:
如果返回
undefined,Render 将抛出一个错误:当组件返回
undefined值时,应用程序将中断。
应用程序显示以下错误:
image.png
你还会注意到控制台中的以下错误:
image.png
卸载组件的
setState给出一个警告:在试图更新卸载组件的状态时,React 可能会警告你内存泄漏。
image.png
严格模式控制台日志消除:从社区反馈中,我们注意到在使用严格模式时,控制台日志消息的消除会造成混乱,因为只显示一个而不是两个。内存消耗:React 17 和更早的版本存在内存泄漏问题,特别是在未挂载的组件中。
- React 18 发生了什么变化?
React 18 更加强调应用程序的并发性。这个概念包括「自动批处理」和「过渡」等功能,以及
createRoot、
hydrateRoot、
renderToPipeableStream和
renderToReadableStream等 API。它还包括
useId、
useTransition、
useDeferredValue、
useSyncExternalStore和
useInsertionEffect等 hooks,以及 Strict Mode 的更新和
ReactDOM.render和
renderToString的弃用。
让我们更深入地看看这些变化?
4.1 Client Rendering
在升级后,你可能需要留意下面列出的控制台警告:
image.png
如果你继续使用 React 17 中支持的
ReactDOM.render()API,你将看到这个警告。通常,我们导入一个组件,并使用
id="app"在
div元素中渲染它。
import ReactDOM from 'react-dom';
import App from 'App';
const app = document.getElementById('app');
ReactDOM.render( , app);在 React 18 中,就像下面的代码样例一样,我们使用了从
"react-dom/client"导入的
createRoot()API:
import { createRoot } from 'react-dom/client';
import App from 'App';
const app = document.getElementById('app');
// 创建根节点
const root = createRoot(app);
// 将 App 渲染到 root
root.render( );4.2 Hydration
React 17 使用了
ReactDOM.hydrate()API 来渲染
hydration,如下面的代码样例所示:
import * as ReactDOM from 'react-dom';
import App from 'App';
const app = document.getElementById('app');
ReactDOM.hydrate( , app);在 React 18 中,
hydration使用了从
"react-dom/client"导入的
hydrateRoot()API,不需要像上面代码片段中那样单独的
render()方法:
import { hydrateRoot } from 'react-dom/client';
import App from 'App';
const app = document.getElementById('app');
const root = hydrateRoot(app, );4.3 Render Callback
你可以在呈现根组件时传递回调函数,以便它在组件呈现或更新后执行。
在 React 17 的渲染方法中,你可以传递一个回调函数作为第三个参数,如下面的代码片段所示:
import * as ReactDOM from 'react-dom';
import App from 'App';
const app = document.getElementById('app');
ReactDOM.render( , app, function() {
// 在初始渲染或任何更新后调用。
console.log('Rendered or Updated');
});回调函数在 React 18 中是不允许的,因为它会通过逐步或部分的
hydration影响应用程序的运行时。相反,你可以在根元素上使用
ref
callback,
setTimeout或
requestIdleCallback,如下面的代码示例所示:
import { createRoot } from 'react-dom/client';
function App({ callback }) {
// 第一次创建 div 时将调用 callback
return (
Hello World
);
}
const app = document.getElementById("root");
const root = createRoot(app);
root.render( console.log("Rendered or Updated")} />); 4.4 自动批处理
在版本 17 之前,状态更新只在 React 事件处理程序中进行批处理。因此,在事件处理程序之外进行的任何状态更新都会导致 re-render,这需要 React 执行额外的后台任务。例如:
const handleClick = () => {
setFirstState("1");
setSecondState("2");
}只有在事件回调函数结束时所有的状态都被更改之后,然后触发一个单独的 re-render,合并所有更新。
promise、原生事件或外部 React 事件处理程序中的状态更新由于丢失了上下文,无法做合并处理,所以每次
setState调用都会触发一次 re-render。例如:
fetch('https://api.com').then(() => {
setFirstState("1");
setSecondState("2");
})
// or
setTimeout(() => {
setFirstState("1");
setSecondState("2");
})在上面的代码片段中,React 将为每次状态更新 re-render。
React 18 中的
createRoot()API 支持批处理所有状态更新,而不管它们发生在应用程序的什么位置。React 在所有状态更新后 re-render 页面。
由于这是一个重大的更改,你可以使用
flushSync()API 停止自动批处理。
import { flushSync } from 'react-dom';
function handleClick() {
flushSync(() => {
setFirstState("1");
});
flushSync(() => {
setSecondState("2");
});
}在上面的代码片段中,
flushSync()的每个实例都更新状态并允许 React re-render。
4.5 Transition
你可以使用 Transition 来区分需要立即更新状态的资源和不需要立即更新状态的资源。
搜索栏的功能就是一个很好的例子。当用户输入搜索词时,你可能希望显示视觉反馈。但是,你不希望在用户完成输入之前就开始搜索。
import { startTransition } from 'react';
// 立即显示当前键入的内容
setSearchCurrentValue(input);
startTransition(() => {
// 不立即显示最后输入的内容
setSearchFinalValue(input);
});在代码片段中,我们没有使用将延迟状态更新的
setTimeout(),而是使用
startTransition()来监视状态更新。
setSearchCurrentValue()只更新与我们希望用户立即获得的反馈相关的状态,
setSearchFinalValue()更新我们希望在用户完成输入后最终进行搜索时使用的状态。
与
setTimeout不同的是,
startTransition更新可以中断,可以跟踪挂起的更新,并且它会立即执行。意味着他们可以被其他紧急渲染所抢占。这种渲染优先级的调整手段可以帮助我们解决各种性能伪瓶颈,提升用户体验。
4.6 放弃对 Internet Explorer 的支持
React 社区也放弃了对 Internet Explorer 的支持,这意味着只有 React 18 之前的版本才能在 Internet Explorer 上运行。现代浏览器功能如
multitask,
promise,
Object.assign或
Symbol在 Internet Explorer 中不会被 polyfill。
- React 18 相对于 React 17 的优点
即使在了解了 React 17 和 React 18 之间的区别之后,你可能仍然不确定是切换到 React 18 还是坚持使用 React 17。
如果一个新版本不能提供比之前版本更多的好处,它就不会受到欢迎。
并发性是 React 18 的主要优势之一。这是一个全新的概念,而不是一个功能,使 React 应用程序运行在 React 18 及更高版本上,优化它们在客户端设备上的性能。
通过在卸载时清除后台任务,React 18 增强了内存管理,降低了内存泄漏的危险。
- 小结
在阅读本文后,你应该能够更新 React 版本并重构代码库以无缝地使用 React 18。为了获得最新的更改和新版本的信息,你还应该密切关注 React 库的更新日志,并与 React 社区保持联系。










