React 18使用围绕并发渲染和悬念构建的新功能 发展了流行的 JavaScript 组件框架。它承诺为进行切换的应用程序提供更好的性能、更多功能和改进的开发人员体验。
在本文中,我们将向您展示如何将现有代码库升级到 React 18。请记住,本指南只是对最广泛适用的更改的概述。对于已经遵循 React 最佳实践的小型项目,迁移应该是相当轻松的;大量复杂的组件可能会引发一些问题,我们将在下面详细介绍。
在做任何其他事情之前,使用 npm 将项目的 React 依赖升级到 v18:
$ npm install react@latest react-dom@latest
新版本在技术上没有任何向后不兼容性。新功能是在选择加入的基础上激活的。由于您尚未更改任何代码,您应该能够启动您的应用程序并观察它正确呈现。您的项目将以其现有的 React 17 行为运行。
$ npm start
在不更改任何代码库的情况下使用 React 18 会产生一个副作用:每次在开发模式下安装应用程序时,您都会看到浏览器控制台警告。
ReactDOM.render is no longer supported in React 18. Use createRoot instead. Until you switch to the new API, your app will behave as if it's running React 17.
如果您还没有准备好升级项目,可以安全地忽略此弃用消息。当您想采用 React 18 功能时,您需要进行它所描述的更改。旧ReactDOM.render()功能已被更面向对象的新根 API 所取代。除了提高易用性外,它还激活了支持所有新标题功能的并发渲染系统。
在您的index.jsorapp.js文件中,查找与以下类似的行:
import App from "./App.js";
import ReactDOM from "react-dom";
const container = document.getElementById("react");
ReactDOM.render(<App />, container);
这是 React 应用程序的典型入口点。它将导入组件的实例呈现App为应用程序的根元素。呈现的内容作为innerHTMLHTML 元素的id=“react”.
要切换到 React 18 根 API,请将上面的代码替换为以下代码:
import App from "./App.js";
import {createRoot} from "react-dom/client";
const container = document.getElementById("react");
const root = createRoot(container);
root.render(<App />);
ReactDOM.render()这与旧API具有等效的效果。React 18 不是初始化根元素并将您的应用程序渲染为单个命令式操作,而是让您首先创建一个根对象,然后显式渲染您的内容。
接下来查找代码中卸载根节点的任何位置。更改为根对象上ReactDOM.unmountComponentAtNode()的新方法:unmount()
// Before
import App from "./App.js";
import ReactDOM from "react-dom";
const container = document.getElementById("react");
ReactDOM.render(<App />, container);
ReactDOM.unmountComponentAtNode(container);
// After
import App from "./App.js";
import {createRoot} from "react-dom/client";
const container = document.getElementById("react");
const root = createRoot(container);
root.render(<App />);
root.unmount();
该ReactDOM.render()方法的可选回调参数在 React 18 根 API 中没有直接对应项。Rendered!在 React 完成渲染根节点后,您之前可以使用此代码登录到控制台:
import App from "./App.js";
import ReactDOM from "react-dom";
const container = document.getElementById("react");
ReactDOM.render(<App />, container, () => console.log("Rendered!"));
此功能已被删除,因为在使用 React 18 的新部分水合和流式服务器渲染功能时,回调调用的时间是不可预测的。如果您已经在使用渲染回调并且需要保持兼容性,您可以使用 refs 机制实现类似的行为:
import {createRoot} from "react-dom/client";
const App = ({callback}) => (
<div ref={callback}>
<h1>Demo App</h1>
</div>
);
const container = document.getElementById("react");
const root = createRoot(container);
root.render(<App callback={() => console.log("Rendered!")} />);
React 在组件挂载时调用函数 refs。在作为根节点的组件上设置 ref 可以让您检测何时渲染发生,提供与旧渲染回调系统类似的效果。
您的应用程序现在应该使用 React 18 功能进行渲染,并且没有任何控制台警告。彻底测试您的应用程序,以确保一切仍按预期运行。如果您发现问题,您也许可以使用这些常见的解决方案来解决它们。
在 React 18 的开发模式下渲染时,封装在组件中的应用程序的行为可能会有所不同。这是因为 Strict Mode 现在测试您的代码库是否支持可重用状态,这一概念将在未来版本中完全引入React。
可重用状态允许 React 重新挂载先前删除的组件,并自动恢复其最后状态。这要求您的组件能够弹性地对效果进行双重调用。现在,严格模式通过在每次使用组件时模拟安装、卸载和重新安装组件来帮助您准备可重用状态,从而解决无法恢复先前状态的任何问题。如果在您的应用程序或其依赖项中发现您尚未准备好解决的问题,您可以禁用严格模式。
React 18 改变了状态更新的“批处理”方式以提高性能。当您在一个函数中多次更改状态值时,React 会尝试将它们组合成一个重新渲染:
const Component = () => {
const [query, setQuery] = useState("");
const [queryCount, setQueryCount] = useState(0);
/**
* Two state updates, only one re-render
*/
setQuery("demo");
setQueryCount(queryCount + 1);
};
这种机制提高了效率,但以前只在 React 事件处理程序中工作。使用 React 18,它适用于所有状态更新,即使它们来自本机事件处理程序、超时或 Promises。如果您在任何这些地方进行连续状态更新,某些代码的行为可能与以前不同。
const Component = () => {
const [query, setQuery] = useState("");
const [queryId, setQueryId] = useState("");
const [queryCount, setQueryCount] = useState(0);
const handleSearch = query => {
fetch(query).then(() => {
setQuery("demo");
setQueryCount(1);
// In React 17, sets to "query-1"
// In React 18, sets to "query-0" - previous state update is batched with this one
setQueryId(`query-${queryCount}`);
});
}
};
在您还没有准备好重构代码的情况下,您可以禁用此行为。包装状态更新flushSync()以强制它们立即提交:
const Component = () => {
const [query, setQuery] = useState("");
const [queryId, setQueryId] = useState("");
const [queryCount, setQueryCount] = useState(0);
const handleSearch = query => {
fetch(query).then(() => {
flushSync(() => {
setQuery("demo");
setQueryCount(1);
});
// Sets to "query-1"
setQueryId(`query-${queryCount}`);
});
}
};
一旦上述所有方面都得到解决,您的应用程序应该与 React 18 完全兼容。尽管还有一些API 表面更改,但这些不应影响大多数应用程序。以下是一些需要注意的事项:
使用服务器端渲染的应用程序需要进行更多更改才能使用 React 18。
// Before
import App from "./App.js";
import ReactDOM from "react-dom";
const container = document.getElementById("react");
ReactDOM.hydrate(<App />, container);
// After
import App from "./App.js";
import {createRoot} from "react-dom/client";
const container = document.getElementById("react");
const root = hydrateRoot(container, <App />);
在您的服务器端代码中,将已弃用的渲染 API调用替换为新的对应项。在大多数情况下,您应该更改renderToNodeStream()为新的renderToReadableStream(). 新的流 API 解锁了对 React 18 的流服务器渲染功能的访问,服务器可以在您的应用程序初始渲染后继续向浏览器提供新的 HTML。
开始使用 React 18 功能
现在您已经升级了,您可以通过整合React 18 功能开始让您的应用程序更强大。React 对并发性的使用意味着组件渲染可以被中断,从而解锁新功能和响应更快的 UI。
一些新增功能包括对 Suspense 的重大更新,一种使用 Transitions 指定状态更新优先级的方法,以及用于限制由非紧急但高频更新引起的重新渲染的内置机制。还有一些其他的更改和改进:您可以undefined从组件的方法返回,关于调用未安装组件render()的警告已被删除,以及一些新的 HTML 属性,例如、和 ,并且可以被 React DOM 的渲染器识别。setState()imageSizesimageSrcSetaria-description
React 18 是稳定的并且可以使用。在大多数情况下,升级过程应该快速简单,只需要更新 npm 并切换到新的根 API。不过,您仍然应该测试所有组件:它们在某些情况下可能表现不同,例如在严格模式下或应用自动批处理时。
这个新版本指出了 React 作为各种 Web 应用程序的高性能框架的未来方向。它还扩展了 React 的服务器端渲染功能,在服务器上添加了 Suspense 以及在初始渲染后将流式内容保留给用户的能力。这为开发人员提供了更大的灵活性,可以在客户端和服务器之间分发渲染。