Post

React 18 并发特性与新功能

React 18 并发特性与新功能

并发渲染

什么是并发渲染?

并发渲染(Concurrent Rendering)是 React 18 最重要的特性。它允许 React 在渲染过程中被中断,让浏览器能够处理其他任务,从而保持应用的响应性。

在 React 17 中,一旦开始渲染,整个过程是不可中断的。如果组件树很大,用户就会感受到卡顿。而 React 18 的并发渲染可以将渲染工作分解成小块,在浏览器需要处理用户交互时暂停渲染。

自动批处理(Automatic Batching)

React 18 扩展了批处理的范围。在 React 17 中,只有在 React 事件处理程序中的多个状态更新才会被批处理。

React 17 的行为:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 这些会被批处理
function handleClick() {
  setCount(c => c + 1);
  setFlag(f => !f);
  // React 只会重新渲染一次
}

// 这些不会被批处理
fetch('/api/data').then(() => {
  setCount(c => c + 1);
  setFlag(f => !f);
  // React 会渲染两次
});

React 18 的改进:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 现在这些也会被批处理!
fetch('/api/data').then(() => {
  setCount(c => c + 1);
  setFlag(f => !f);
  // React 只会重新渲染一次
});

// 如果你想要同步更新,可以使用 flushSync
import { flushSync } from 'react-dom';

flushSync(() => {
  setCount(c => c + 1);
});
// DOM 已经更新
flushSync(() => {
  setFlag(f => !f);
});
// DOM 再次更新

新的 Root API

React 18 引入了新的 createRoot API 来替代 ReactDOM.render

React 17 的方式:

1
2
3
4
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(<App />, document.getElementById('root'));

React 18 的方式:

1
2
3
4
5
import { createRoot } from 'react-dom/client';
import App from './App';

const root = createRoot(document.getElementById('root'));
root.render(<App />);

使用新的 Root API 是启用并发特性的前提。如果继续使用旧的 ReactDOM.render,React 18 会以传统模式运行,不会启用并发特性。

Suspense 的改进

服务端渲染中的 Suspense

React 18 在服务端渲染中增强了 Suspense 的功能。现在可以在服务端使用 Suspense 来包装尚未准备好的组件。

1
2
3
4
5
6
7
8
9
import { Suspense } from 'react';

function App() {
  return (
    <Suspense fallback={<div>Loading comments...</div>}>
      <Comments />
    </Suspense>
  );
}

流式 SSR

React 18 引入了流式服务端渲染,可以更早地发送 HTML 给客户端:

1
2
3
4
5
6
7
8
9
10
11
// 服务端
import { renderToPipeableStream } from 'react-dom/server';

app.use('/', (request, response) => {
  const { pipe } = renderToPipeableStream(<App />, {
    onShellReady() {
      response.setHeader('content-type', 'text/html');
      pipe(response);
    }
  });
});

新的 Hooks

useId

useId 用于生成唯一的 ID,在服务端渲染中特别有用:

1
2
3
4
5
6
7
8
9
10
11
12
import { useId } from 'react';

function Form() {
  const id = useId();
  
  return (
    <div>
      <label htmlFor={id}>姓名:</label>
      <input id={id} type="text" />
    </div>
  );
}

useTransition

useTransition 让你可以将状态更新标记为过渡,React 会优先处理更紧急的更新:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { useTransition, useState } from 'react';

function SearchPage() {
  const [isPending, startTransition] = useTransition();
  const [searchTerm, setSearchTerm] = useState('');
  
  function handleChange(e) {
    startTransition(() => {
      // 这个更新的优先级较低
      setSearchTerm(e.target.value);
    });
  }
  
  return (
    <div>
      <input onChange={handleChange} />
      {isPending && <div>搜索中...</div>}
      <SearchResults query={searchTerm} />
    </div>
  );
}

useDeferredValue

useDeferredValue 让你可以延迟更新 UI 的非关键部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { useDeferredValue, useState } from 'react';

function SearchPage() {
  const [searchTerm, setSearchTerm] = useState('');
  const deferredSearchTerm = useDeferredValue(searchTerm);
  
  return (
    <div>
      <input 
        value={searchTerm}
        onChange={e => setSearchTerm(e.target.value)}
      />
      <SearchResults query={deferredSearchTerm} />
    </div>
  );
}

严格模式的变化

React 18 的严格模式在开发环境中会故意双重挂载组件,以帮助发现副作用问题。这意味着 useEffectuseState 的初始化函数等可能会被调用两次。 可以通过在组件树的任何位置使用 组件来为它的所有后代组件开启严格模式。

1
2
3
4
5
6
7
8
9
10
11
12
function MyComponent() {
  useEffect(() => {
    // 在严格模式下,这个 effect 会运行两次
    console.log('组件挂载');
    
    return () => {
      console.log('组件卸载');
    };
  }, []);
  
  return <div>我的组件</div>;
}

这个变化帮助开发者编写更加健壮的代码,确保组件能够正确处理重新挂载的情况。

升级建议

从 React 17 升级到 React 18 相对简单:

  1. 安装 React 18
    1
    
    npm install react@18 react-dom@18
    
  2. 更新 Root API
    1
    2
    3
    4
    5
    6
    
    // 旧的方式
    ReactDOM.render(<App />, container);
       
    // 新的方式
    const root = createRoot(container);
    root.render(<App />);
    
  3. 更新类型定义(如果使用 TypeScript)
    1
    
    npm install @types/react@18 @types/react-dom@18
    

性能提升

React 18 在性能方面有显著提升:

  • 并发特性让应用在处理大量数据时保持响应
  • 自动批处理减少了不必要的重新渲染
  • Suspense 改进提供了更好的加载体验
  • 流式 SSR改善了首屏加载时间
This post is licensed under CC BY 4.0 by the author.