跳到主要内容

迁移到 React 18

· 阅读需 3 分钟
Chen Siwei

并发 React

在以前的 React 中,渲染是不可中断的,一旦状态更新,React 就会埋头渲染(包括同步到 DOM 树),渲染完才能进行处理新的状态更新(或者做别的事)。这种情况下如果一秒内产生了上百次的状态更新,页面会直接卡死。

而在并发 React 中,渲染是可中断的,一旦状态更新,React 会先把渲染任务放到一个队列中,然后开始处理新的状态更新,等到空闲的时候再去渲染队列中的任务。这样就不会出现页面卡死的情况了。

由于并发 React 的特性,升级到 React 18 后可能会导致组件的行为发生变化。

升级到 React 18

客户端渲染

ReactDOM.render 在 18 中不再支持,但仍可以使用,它的运行方式与 17 保持一致,即不支持并发渲染。

在 18 中,应当使用 createRoot 来创建一个要渲染的根结点,然后再执行渲染:

import { createRoot } from 'react-dom/client';

const container = document.getElementById('app');
const root = createRoot(container); // TS 中为 `createRoot(container!)`
root.render(<App tab="home" />);

ReactDOM.unmountComponentAtNode 也同样被弃用了,使用 root.unmount() 代替。

更多见 Replacing render with createRoot

TypeScript 支持

需升级 @types/react@types/react-dom

状态批处理

在 17 中,React 只会对 onClick 等自身的事件监听进行状态批处理。而在 18 中,React 会对 setTimeoutPromise 和事件监听等的执行回调进行状态批处理:

setTimeout(() => {
setCount((c) => c + 1);
setFlag((f) => !f);
// React 会把这两个状态更新合并为一个
}, 1000);

这个特性可能会改变组件的行为,如果你想照旧各自触发更新,可以用 flushSync 包裹:

import { flushSync } from 'react-dom';

setTimeout(() => {
flushSync(() => {
setCount((c) => c + 1);
});
flushSync(() => {
setFlag((f) => !f);
});
}, 1000);

Strict Mode

在升级到 18 时,可以用 <StrictMode> 来检测由于并发特性而导致的 bug ,它只在开发环境下生效。同时 18 的 <StrictMode> 自身也做了更新,它会在第一次挂载后模拟组件的卸载和重新挂载,因此被 <StrictMode> 包裹的组件可能会触发两遍 useEffect

其他值得注意的变动

  • 组件可以渲染 undefined 了。

参考