由于更新经常包括完全改变特性的修改,甚至删除某些特性并添加其他特性,一些开发人员可能会发现很难在不同版本的库之间进行转换。最好使用库的最新版本,以获得尽可能好的性能。
这篇文章将讨论 React 18 是什么,React 17 的问题,React 18 的新特性,以及为什么你应该使用最新版本。
在我们讨论“React 18有什么新功能”之前,React 18 是什么意思?任何 18.0.0 以上但不包括 19.0.0 的 React 库的稳定版本都被称为 React 18。
React 18 的创建在 React 应用程序中引入了并发渲染。React 一直在关注 DOM 渲染,并为开发人员提供控制和跟踪组件生命周期的工具。有了一些新功能,React 18 现在可以调整渲染过程,以适应客户端设备。
React 社区提供了多种安装选项。要在应用程序中安装 React 18,可以在 HTML 脚本标记中使用 CDN URL 作为源(src
)。
<script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin>script> <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin>script>
<script src="app.js">script>
body>
通过在工作目录的终端中执行以下命令,你可以使用 NPM 或 Yarn 升级或安装 React 18,用于单页面和绑定应用程序。NPM:
npm install react react-dom
Yarn:
yarn add react react-dom
上面的命令将自动检测并安装或升级开发环境中最新的 React 和 React DOM 版本。
React 社区已经注意到库中存在一些需要改进的问题。如果 React 17 功能完美,React 18 和更高版本就不需要发布了。
根据 React 18.0.0 的更新日志,React 17 或更早版本的以下问题得到了解决:
undefined
,Render 将抛出一个错误:当组件返回 undefined
值时,应用程序将中断。应用程序显示以下错误:
你还会注意到控制台中的以下错误:
setState
给出一个警告:在试图更新卸载组件的状态时,React 可能会警告你内存泄漏。React 18 更加强调应用程序并发性。这个想法包括自动批处理和过渡等功能,以及 createRoot
、hydrateRoot
、renderToPipeableStream
和 renderToReadableStream
等 API。它还包括 useId
、useTransition
、useDeferredValue
、useSyncExternalStore
和 useInsertionEffect
等 hooks,以及 Strict Mode 的更新和 ReactDOM.render
和 renderToString
的弃用。
让我们更深入地看看这些变化
在升级后,你可能需要留意下面列出的控制台警告:
如果你继续使用 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 />, app);
在 React 18 中,就像下面的代码样例一样,我们使用了从 "react-dom/client"
导入的 createRoot()
API:
import {creatRoot}from 'react-dom/client';
import App from 'App';
const app = document.getElementById('app');
// 创建根节点
const root = createRoot(app);
// 将 app 渲染到 root
root.render(<App />);
React 17 使用了 ReactDOM.hydrate()
API 来渲染 hydration
,如下面的代码样例所示:
import * as ReactDOM from 'react-dom';
import App from 'App';
const app = document.getElementById('app');
ReactDOM.hydrate(<App tab="home" />, 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, <App tab="home" />);
你可以在呈现根组件时传递回调函数,以便它在组件呈现或更新后执行。
在 React 17 的渲染方法中,你可以传递一个回调函数作为第三个参数,如下面的代码片段所示:
import * as ReactDOM from 'react-dom';
import App from 'App';
const app = document.getElementById('app');
ReactDOM.render(app, <App tab="home" />, 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 (
<div ref={callback}>
<h1>Hello World</h1>
</div>
);
}
const app = document.getElementById("root");
const root = createRoot(app);
root.render(<App callback={() => console.log("Rendered or Updated")} />);
在版本 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。
你可以使用 Transition 来区分需要立即更新状态的资源和不需要立即更新状态的资源。
搜索栏的功能就是一个很好的例子。当用户输入搜索词时,你可能希望显示视觉反馈。但是,你不希望在用户完成输入之前就开始搜索。
import { startTransition } from 'react';
// 立即显示当前键入的内容
setSearchCurrentValue(input);
startTransition(() => {
// 不立即显示最后输入的内容
setSearchFinalValue(input);
});
在代码片段中,我们没有使用将延迟状态更新的 setTimeout()
,而是使用 startTransition()
来监视状态更新。setSearchCurrentValue()
只更新与我们希望用户立即获得的反馈相关的状态,setSearchFinalValue()
更新我们希望在用户完成输入后最终进行搜索时使用的状态。
与 setTimeout
不同的是,startTransition
更新可以中断,可以跟踪挂起的更新,并且它会立即执行。意味着他们可以被其他紧急渲染所抢占。这种渲染优先级的调整手段可以帮助我们解决各种性能伪瓶颈,提升用户体验。
React 社区也放弃了对 Internet Explorer 的支持,这意味着只有 React 18 之前的版本才能在 Internet Explorer 上运行。现代浏览器功能如 multitask
,promise
,Object.assign
或 Symbol
在 Internet Explorer 中不会被 pollyfill。
即使在了解了 React 17 和 React 18 之间的区别之后,你可能仍然不确定是切换到 React 18 还是坚持使用 React 17。
如果一个新版本不能提供比之前版本更多的好处,它就不会受到欢迎。
并发性是 React 18 的主要优势之一。这是一个全新的概念,而不是一个功能,使 React 应用程序运行在 React 18 及更高版本上,优化它们在客户端设备上的性能。
通过在卸载时清除后台任务,React 18 增强了内存管理,降低了内存泄漏的危险。
在阅读本文后,你应该能够更新 React 版本并重构代码库以无缝地使用 React 18。为了获得最新的更改和新版本的信息,你还应该密切关注 React 库的更新日志,并与 React 社区保持联系。