这是 React 官方 2022.03.08 发表的文章《How to Upgrade to the React 18 Release Candidate[1]》的译文,通过本文,可以对 React 18 的新特性有一个全面的认知。
接下来,我还会翻译其它几篇比较重要的 React 18 文章,以便以更好的姿势使用 React 18,关注不迷路。
今天,我们发布了 React 18 RC 版本。正如我们在 React Conf[2] 上分享的那样,React 18 基于 concurrent 模式,带来了更多能力,同时提供了渐进升级的方法。在这篇文章中,我们会一步一步的带您升级到 React 18。
安装
使用 @rc标签来安装最新版 React。
## npm
$ npm install react@rc react-dom@rc
## yarn
$ yarn add react@rc react-dom@rc
客户端渲染 API 更新
当你首次安装 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. Learn more: https://reactjs.org/link/switch-to-createroot[3]。
React 18 提供了更合理的初始化 API,使用该 API,会自动启用 concurrent 模式:
// Before
import { render } from 'react-dom';
const container = document.getElementById('app');
render(<App tab="home" />, container);
// After
import { createRoot } from 'react-dom/client';
const container = document.getElementById('app');
const root = createRoot(container);
root.render(<App tab="home" />);
同时我们将卸载方法从 unmountComponentAtNode 修改为 root.unmount:
// Before
unmountComponentAtNode(container);
// After
root.unmount();
我们移除了 ReactDOM.render 函数的 callback,因为当使用 Susponse 的时候,它会有问题:
// Before
const container = document.getElementById('app');
ReactDOM.render(<App tab="home" />, container, () => {
console.log('rendered');
});
// After
function AppWithCallbackAfterRender() {
useEffect(() => {
console.log('rendered');
});
return <App tab="home" />
}
const container = document.getElementById('app');
const root = ReactDOM.createRoot(container);
root.render(<AppWithCallbackAfterRender />);
最后,如果你使用 hydration 来实现了 SSR,需要将 hydrate 替换为 hydrateRoot:
// Before
import { hydrate } from 'react-dom';
const container = document.getElementById('app');
hydrate(<App tab="home" />, container);
// After
import { hydrateRoot } from 'react-dom/client';
const container = document.getElementById('app');
const root = hydrateRoot(container, <App tab="home" />);
// Unlike with createRoot, you don't need a separate root.render() call here.
更多信息可见 Replacing render with createRoot[4]。
SSR API 更新
在 React 18 中,为了支持服务端的 Suspense 和流式 SSR,优化了 react-dom/server 的 API。
使用以下 API,将会抛出警告:
- renderToNodeStream:废弃 ⛔️️。
相反,对于 Node 环境中的流式传输,请使用:
- renderToPipeableStream:新增 ✨。
我们还引入了一个新的 API,以在现代边缘运行时环境支持流式 SSR 和 Suspense,例如 Deno 和 Cloudflare workers:
- renderToReadableStream:新增 ✨。
下面的两个 API 可以继续使用,但是不支持 Suspense:
- renderToString:限制 ⚠️。
- renderToStaticMarkup:限制 ⚠️。
下面的 API 没有变化:
- renderToStaticNodeStream。
更多信息可见Upgrading to React 18 on the server[5]、New Suspense SSR Architecture in React 18[6]。
自动批处理 Automatic Batching
批处理是指:React 将多个状态更新,聚合到一次 render 中执行,以提升性能。
在 React 18 之前,只能在 React 自己的事件机制中使用批处理,而在 Promise、setTimeout、原生事件等场景下,是不能使用批处理的。
React 18 支持了更多场景下的批处理,以提供更好的性能。
// 在 React 18 之前,只有 React 事件,才会使用批处理
function handleClick() {
setCount(c => c + 1);
setFlag(f => !f);
// React 只会 re-render 一次,这就是批处理
}
setTimeout(() => {
setCount(c => c + 1);
setFlag(f => !f);
// React 会 render 两次,每次 state 变化更新一次
}, 1000);
使用 createRoot初始化 React 18 之后,所有的状态更新,会自动使用批处理,不关心应用场景。
// React 18 之后,Promise、setTimeout、原生事件中,都会自动批处理
function handleClick() {
setCount(c => c + 1);
setFlag(f => !f);
// React 只会 re-render 一次,这就是批处理
}
setTimeout(() => {
setCount(c => c + 1);
setFlag(f => !f);
// React 只会 re-render 一次,这就是批处理
}, 1000);
这是一个 break change,但是我们希望这能提升你的产品性能。当然,你仍然可以使用 flushSync 来手动取消批处理,强制同步执行:
import { flushSync } from 'react-dom';
function handleClick() {
flushSync(() => {
setCounter(c => c + 1);
});
// React 更新一次 DOM
flushSync(() => {
setFlag(f => !f);
});
// React 更新一次 DOM
}
更多信息可见 Automatic batching for fewer renders in React 18[7]。
三方库 API
在 React 18 中,我们和三方库作者合作,定义了一些新的 API,以满足三方库在 concurrent 模式下特定场景的诉求。比如 styles 管理、外部状态管理、可访问性(accessibility)等场景。
为了支持 React 18,一些三方库可能需要用到下面的 API:
- useId 是一个新的 Hook,支持在客户端和服务端生成唯一的 ID,同时避免 hydration 的不兼容。它可以解决在 React 17 。 及更低版本一直存在的问题。在 React 18 中,这个问题尤为重要,因为流式 SSR 返回的 HTML 片段是无序的。更多信息可见 Intent to Ship: useId[8]。
- useSyncExternalStore是一个新的 Hook,允许外部状态管理器,强制立即同步更新,以支持并发读取。这个新的 API 推荐用于所有 。 React 外部状态管理库。详情见 useSyncExternalStore overview post[9]、useSyncExternalStore API details[10]。
- useInsertionEffect是一个新的 Hook,它可以解决 CSS-in-JS 库在渲染中动态注入样式的性能问题。除非你已经构建了一个 CSS-in-JS 库,否则我们不希望你使用它。这个 Hook 执行时机在 DOM 生成之后,Layout Effect 执行之前。更多信息可见 Library Upgrade Guide for style[11]。
React 18还为 concurrent 渲染引入了新的 API,例如 startTransition 和 useDeferredValue,在即将发布的稳定版本中会分享更多相关内容。
严格模式 Strict Mode
未来,我们希望添加一个功能,允许 React 保存组件的状态,但移除 UI 部分。比如在返回旧的页面时,React 立即恢复之前的内容。为此,React 将使用之前保留的状态重新加载组件。
这个功能会给 React 项目带来非常好的体验,但要求组件支持 state 不变的情况下,组件多次卸载和重载。
为了检查出不合适的组件写法,React 18 在开发模式渲染组件时,会自动执行一次卸载,再重新加载的行为,以便检查组件是否支持 state 不变,组件卸载重载的场景。
在以前,React 加载组件的逻辑为:
* React mounts the component.
* Layout effects are created.
* Effect effects are created.
在 React 18 严格模式的开发环境,React 会模拟卸载并重载组件:
* React mounts the component.
* Layout effects are created.
* Effect effects are created.
* React simulates unmounting the component.
* Layout effects are destroyed.
* Effects are destroyed.
* React simulates mounting the component with the previous state.
* Layout effect setup code runs
* Effect setup code runs
更多信息可见:Adding Strict Effects to Strict Mode[12] 、How to Support Strict Effects[13]。
配置测试环境
当你第一次在测试用例中使用 createRoot时候,你会看到以下警告:
- The current testing environment is not configured to support act(…)。
为了修复这个问题,你需要在执行用例之前设置 globalThis.IS_REACT_ACT_ENVIRONMENT为 true。
// In your test setup file
globalThis.IS_REACT_ACT_ENVIRONMENT = true;
这个标记告诉 React,它在一个类似单元测试的环境中运行。如果你忘了使用 act,React 将打印一些有用的警告。你也可以将标志设置为 false 来告诉 React 不需要 act。这对于模拟浏览器环境的端到端测试很有用。当然,我们希望测试库会自动为您加上这个配置。例如,下一个版本的 React Testing Library 内置了对 React 18 的支持,无需任何额外配置。
更多信息可见:More background on the the act testing API and related changes[14]。
移除了 IE 支持
在此版本中,React 将放弃对 Internet Explorer 的支持。我们进行此更改是因为 React 18 中引入的新功能是基于现代浏览器开发的,部分能力在 IE 上是不支持的,比如 microtasks。
如果您需要支持 Internet Explorer,我们建议您继续使用 React 17。
其它变更
- 移除“setState on unmounted component” 警告[15]。
- Susponse 不再强制需要 fallback 属性[16]。
- 组件支持 render undefined[17]。
- 废弃 renderSubtreeIntoContainer[18]。
- 严格模式不再忽略自动二次渲染组件的 log[19]。
参考资料
[1]How to Upgrade to the React 18 Release Candidate: https://reactjs.org/blog/2022/03/08/react-18-upgrade-guide.html。
[2]React Conf: https://reactjs.org/blog/2021/12/17/react-conf-2021-recap.html。
[3]https://reactjs.org/link/switch-to-createroot: https://reactjs.org/link/switch-to-createroot。
[4]Replacing render with createRoot: https://github.com/reactwg/react-18/discussions/5。
[5]Upgrading to React 18 on the server: https://github.com/reactwg/react-18/discussions/22。
[6]New Suspense SSR Architecture in React 18: https://github.com/reactwg/react-18/discussions/37。
[7]Automatic batching for fewer renders in React 18: https://github.com/reactwg/react-18/discussions/21。
[8]Intent to Ship: useId: https://github.com/reactwg/react-18/discussions/111。
[9]useSyncExternalStore overview post: https://github.com/reactwg/react-18/discussions/70。
[10]useSyncExternalStore API details: https://github.com/reactwg/react-18/discussions/86。
[11]Library Upgrade Guide for style: https://github.com/reactwg/react-18/discussions/110。
[12]Adding Strict Effects to Strict Mode: https://github.com/reactwg/react-18/discussions/19。
[13]How to Support Strict Effects: https://github.com/reactwg/react-18/discussions/18。
[14]More background on the the act testing API and related changes: https://github.com/reactwg/react-18/discussions/102。
[15]移除“setState on unmounted component” 警告: https://github.com/reactwg/react-18/discussions/82。
[16]Susponse 不再强制需要 fallback 属性: https://github.com/reactwg/react-18/discussions/72。
[17]组件支持 render undefined: https://github.com/reactwg/react-18/discussions/75。
[18]废弃 renderSubtreeIntoContainer: https://github.com/facebook/react/pull/23355。
[19]严格模式不再忽略自动二次渲染组件的 log: https://github.com/reactwg/react-18/discussions/96。