React 18 有哪些新特性?

一、是什么

React 18 是 React 的一次重大版本更新,于 2022 年 3 月正式发布。它引入了并发渲染(Concurrent Rendering)作为核心底层能力,并在此基础上提供了一系列新 API 和改进,包括自动批处理、新的渲染入口、并发相关 Hooks、Suspense 增强等。

React 18 的设计理念是渐进式升级——大部分应用可以直接升级且无需改动代码,同时可以按需采用新的并发特性。

二、新的渲染入口

2.1 createRoot

React 18 引入了新的客户端渲染 API createRoot,替代旧的 ReactDOM.render

// React 17(旧 API,React 18 中仍可用但已废弃)
import ReactDOM from 'react-dom';
ReactDOM.render(<App />, document.getElementById('root'));

// React 18(新 API)
import { createRoot } from 'react-dom/client';
const root = createRoot(document.getElementById('root'));
root.render(<App />);

使用 createRoot 的关键意义:

  • 启用 React 18 的所有新特性(包括并发渲染)
  • 支持 root.unmount() 卸载整个应用
  • 移除了 render 的回调参数(可用 useEffect 替代)

2.2 hydrateRoot

用于服务端渲染(SSR)场景的水合操作:

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

const root = hydrateRoot(
  document.getElementById('root'),
  <App />
);

createRoot 不同,hydrateRoot 在调用时就传入 JSX,因为初始的客户端渲染需要与服务端输出匹配。

三、自动批处理(Automatic Batching)

3.1 React 17 的批处理局限

在 React 17 中,只有 React 事件处理函数内的状态更新会被批处理,setTimeoutPromise、原生事件中的更新不会:

function Counter() {
  const [count, setCount] = useState(0);
  const [flag, setFlag] = useState(false);

  function handleClick() {
    // React 17:React 事件中,这两次更新会被批处理(只触发一次渲染)
    setCount(c => c + 1);
    setFlag(f => !f);
  }

  function handleAsync() {
    setTimeout(() => {
      // React 17:setTimeout 中,每次 setState 都会触发一次渲染(共两次)
      setCount(c => c + 1);
      setFlag(f => !f);
    }, 0);
  }

  return <button onClick={handleClick}>{count}</button>;
}

3.2 React 18 的自动批处理

React 18 使用 createRoot 后,所有状态更新都会自动批处理,无论触发位置:

function Counter() {
  const [count, setCount] = useState(0);
  const [flag, setFlag] = useState(false);

  function handleAsync() {
    setTimeout(() => {
      // React 18:自动批处理,只触发一次渲染
      setCount(c => c + 1);
      setFlag(f => !f);
    }, 0);

    fetch('/api/data').then(() => {
      // Promise 回调中同样自动批处理
      setCount(c => c + 1);
      setFlag(f => !f);
    });
  }

  return <button onClick={handleAsync}>{count}</button>;
}

如果确实需要在某次更新后立即触发重渲染,可以使用 flushSync

import { flushSync } from 'react-dom';

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

四、新的 Hooks

4.1 useTransition

将状态更新标记为非紧急(transition),使 React 可以中断该更新以优先处理更紧急的操作(如用户输入):

import { useState, useTransition } from 'react';

function SearchPage() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  const [isPending, startTransition] = useTransition();

  function handleChange(e) {
    setQuery(e.target.value);
    startTransition(() => {
      setResults(filterLargeList(e.target.value));
    });
  }

  return (
    <div>
      <input value={query} onChange={handleChange} />
      {isPending ? <p>加载中...</p> : <ResultList results={results} />}
    </div>
  );
}

4.2 useDeferredValue

延迟一个值的更新,使其"滞后"于主要的状态变化:

import { useState, useDeferredValue } from 'react';

function SearchResults({ query }) {
  const deferredQuery = useDeferredValue(query);
  const isStale = query !== deferredQuery;

  return (
    <div style={{ opacity: isStale ? 0.6 : 1 }}>
      <HeavyList filter={deferredQuery} />
    </div>
  );
}

4.3 useId

生成在服务端和客户端保持一致的唯一 ID,解决 SSR 水合时 ID 不匹配的问题:

import { useId } from 'react';

function FormField({ label }) {
  const id = useId();

  return (
    <div>
      <label htmlFor={id}>{label}</label>
      <input id={id} type="text" />
    </div>
  );
}

4.4 useSyncExternalStore

用于订阅外部数据源,确保在并发渲染下数据的一致性(主要供第三方状态库使用):

import { useSyncExternalStore } from 'react';

function useWindowWidth() {
  return useSyncExternalStore(
    (callback) => {
      window.addEventListener('resize', callback);
      return () => window.removeEventListener('resize', callback);
    },
    () => window.innerWidth,
    () => 1024 // SSR 回退值
  );
}

function ResponsiveComponent() {
  const width = useWindowWidth();
  return <div>窗口宽度:{width}px</div>;
}

4.5 useInsertionEffect

在 DOM 变更之后、布局 effect 之前同步执行,专为 CSS-in-JS 库注入样式设计:

import { useInsertionEffect } from 'react';

function useCSS(rule) {
  useInsertionEffect(() => {
    const style = document.createElement('style');
    style.textContent = rule;
    document.head.appendChild(style);
    return () => style.remove();
  }, [rule]);
}

执行顺序:useInsertionEffect → DOM 变更 → useLayoutEffect → 浏览器绘制 → useEffect

五、Suspense 增强

5.1 SSR 流式渲染

React 18 的 Suspense 支持服务端的流式 HTML 渲染和选择性水合:

// server.js
import { renderToPipeableStream } from 'react-dom/server';

app.get('/', (req, res) => {
  const { pipe } = renderToPipeableStream(
    <App />,
    {
      bootstrapScripts: ['/main.js'],
      onShellReady() {
        res.statusCode = 200;
        res.setHeader('Content-type', 'text/html');
        pipe(res);
      },
    }
  );
});
function App() {
  return (
    <Layout>
      <NavBar />
      <Suspense fallback={<Spinner />}>
        <MainContent />
      </Suspense>
      <Suspense fallback={<SkeletonSidebar />}>
        <Sidebar />
      </Suspense>
    </Layout>
  );
}

流式渲染的工作方式:

  1. 服务端先发送 Shell(NavBar 等已就绪部分)的 HTML
  2. 当 Suspense 边界内的组件就绪后,以流的方式发送对应 HTML 片段
  3. 客户端选择性水合——用户交互的区域优先水合

5.2 Suspense 在客户端的改进

React 18 中 Suspense 在客户端的行为也有变化:挂起的组件不再以"可见但隐藏"的方式渲染,而是在 fallback 状态下不挂载子组件,直到数据就绪。

六、Strict Mode 双重调用

React 18 的严格模式在开发环境下,会对组件执行双重挂载(mount → unmount → mount)来帮助发现 effect 清理逻辑中的问题:

// 开发模式下的执行顺序:
// 1. 组件挂载,effect 执行
// 2. 组件卸载,cleanup 执行
// 3. 组件再次挂载,effect 再次执行
useEffect(() => {
  const connection = createConnection(roomId);
  connection.connect();
  return () => connection.disconnect(); // 必须正确清理
}, [roomId]);

这一行为旨在提前暴露:

  • 忘记编写 cleanup 函数
  • cleanup 函数没有正确镜像 setup 逻辑
  • 为 React 未来的 Offscreen API(组件保持状态的挂载/卸载)做准备

七、废弃与移除

废弃项替代方案
ReactDOM.rendercreateRoot
ReactDOM.hydratehydrateRoot
ReactDOM.unmountComponentAtNoderoot.unmount()
ReactDOM.renderSubtreeIntoContainer使用 Portal
render 回调useEffect / requestIdleCallback

八、升级策略

// 最小升级步骤:只需修改入口文件
// 1. 安装 React 18
// npm install react@18 react-dom@18

// 2. 修改入口
import { createRoot } from 'react-dom/client';

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

// 3. 按需逐步采用并发特性(useTransition, useDeferredValue 等)

React 18 的升级是渐进式的:使用 createRoot 后,现有代码继续以同步方式运行,只有在显式使用并发特性(如 startTransition)时才会进入并发渲染模式。

九、总结

特性说明
并发渲染底层能力,使渲染可中断、可恢复
自动批处理所有场景下的状态更新自动合并
Transitions区分紧急/非紧急更新,提升交互响应性
新 HooksuseId、useTransition、useDeferredValue 等
Suspense 增强支持 SSR 流式渲染和选择性水合
新渲染入口createRoot / hydrateRoot
Strict Mode双重 effect 调用,提前暴露清理问题

React 18 是 React 并发时代的开端,它为后续的 Server Components、React Compiler 等特性奠定了基础。