React面试宝典【Part4:React 18 核心特性】

2026-08-03 React,面试

1. createRoot 替代 ReactDOM.render

  • 旧 API:ReactDOM.render(<App />, rootEl)
  • 新 API:createRoot(rootEl).render(<App />)

例子

旧模式:ReactDOM.render(React 17/18 兼容模式)

import ReactDOM from 'react-dom';
import App from './App';

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

特点:

  • 不开启并发渲染
  • 自动批处理不完全生效
  • 行为基本和 React 17 一致
  • 不会完整使用 React 18 新特性

新模式:createRoot(React 18 推荐,开启并发)

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

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

特点:

  • 真正启用 React 18 并发能力
  • 完整自动批处理
  • 支持 Suspense、useTransition、useDeferredValue
  • 严格模式下 useEffect 执行两次
  • 流式 SSR、选择性水合

核心意义:

  • 只有用 createRoot 才会真正开启 React 18 并发特性
  • ReactDOM.render 仍然兼容,但行为保持 React 17 旧模式,不支持并发、自动批处理等新能力

核心一句话:

createRoot 是并发渲染的开关。


2. 并发渲染 Concurrent Rendering

关键点

  • 不是 Web Worker,不是多线程,仍然是单主线程
  • 基于 Fiber 架构 + 时间分片
  • 把大渲染任务切成小片,每执行一小段就判断是否还有剩余时间
  • 有高优先级任务(输入、点击、拖拽)来时,立即暂停低优先级任务
  • 等浏览器空闲再继续,保证 UI 永远不卡死

带来的能力

  • 页面不再卡顿
  • 可中断、可恢复、可重入、可插队
  • 为 Suspense、useTransition、useDeferredValue 提供底层支持

核心一句话:

React 可以中断渲染,把主线程还给浏览器,高优先级任务可以插队。


3. 自动批处理 Automatic Batching

旧行为(React 17 及以前)
只有在 合成事件 / 生命周期 里的 setState 才会批量更新
setTimeoutPromise、原生事件里的 setState 不会合并,会多次渲染

React 18 行为(自动批处理)
无论在哪里调用 setState,都会自动合并为一次更新

包括:

  • setTimeout
  • Promise.then / async
  • 原生 DOM 事件
  • 任何异步回调

好处:
大幅减少重复渲染,提升性能。

如果确实想退出批处理(极少场景):

import { flushSync } from 'react-dom';
flushSync(() => {
  setCount(c => c + 1);
});

4. useTransition / useDeferredValue(优先级调度)

两者都是让某些更新变成低优先级,不阻塞 UI,只是用法不同。

useTransition
用于包裹 setState,标记为低优先级更新

const [isPending, startTransition] = useTransition();

startTransition(() => {
  setLargeList(newList); // 昂贵更新
});
  • 高优先级:输入框、点击
  • 低优先级:大数据列表渲染、过滤、Tab 切换
  • isPending 可展示 loading

useDeferredValue

用于延迟某个值的更新,适合无法控制 setState 的场景(如 props)

const deferredQuery = useDeferredValue(query);
const result = filterBigList(deferredQuery);
  • 不阻塞输入
  • 值会在空闲时才更新

区别一句话

  • useTransition:控制更新动作
  • useDeferredValue:控制值本身

5. 严格模式下 Effect 执行两次(开发环境)

只在 development + strict mode 下出现,生产环境不会。

为什么?
React 18 想让开发者提前适应:

  • 组件可能被快速卸载、重挂载
  • Effect 必须能正确处理多次执行、清理逻辑
  • 强制你写干净的清理函数,避免内存泄漏、重复请求

表现

useEffect(() => {
  console.log('run effect');
  return () => console.log('clean');
}, []);

log输出:

run effect
clean
run effect

目的
让你提前发现:

  • 没清理的定时器
  • 重复请求
  • 重复订阅
  • 状态错乱

6. 服务端渲染 SSR 改进 + Suspense 支持

React 18 对 SSR 完全重写,叫 Streaming SSR

核心改进:

  • HTML 可以分段流式返回,不用等所有数据
  • Suspense 支持服务端数据等待,数据回来再流式注入 HTML
  • 水合(hydrate)可分段进行,不必等全部 JS 下载完
  • 优先渲染高优内容,提升页面可交互速度

关键词:
流式渲染、选择性水合、Suspense for SSR、数据获取不阻塞页面

补充

什么是水合(hydrate)

一句话定义
水合 = 服务端吐出的静态 HTML + 客户端 JS 绑定事件,让页面“活过来”的过程。

1. 先搞懂 SSR 流程

  1. 服务端渲染 React 组件 → 生成纯静态 HTML
  2. 返回给浏览器 → 页面立刻显示内容(快)
  3. 浏览器加载 JS(React 代码)
  4. React 把事件、状态、逻辑绑到已有 DOM 上 → 这一步就是 Hydrate 水合

没有水合:

  • 页面能看
  • 按钮点不动
  • 输入框输不了
  • 完全是死页面

有水合:

  • 页面可交互
  • 有 state
  • 有事件
  • 变成正常 React 应用

2. 水合做了什么?

  1. 不重新创建 DOM
    复用服务端已经渲染好的 HTML,不重新渲染。

  2. 绑定事件监听
    onClick、onChange、合成事件系统。

  3. 建立组件树、Fiber 树
    让 React 知道 DOM 对应哪个组件。

  4. 初始化 state、context
    让应用进入可运行状态。

3. 为什么叫“水合”?

  • 服务端渲染:只有结构,没有生命 → 干的 HTML
  • 客户端注入 JS 逻辑、事件、状态 → 加水让它活起来
    所以叫 Hydrate(水合)。

4. hydrate vs render 区别

  • ReactDOM.render
    清空容器 → 从头创建 DOM → 渲染

  • ReactDOM.hydrate(React 17及以前)
    复用已有 HTML → 只绑定事件 → 水合

  • React 18
    统一为 hydrateRoot,支持流式、选择性水合。

5. React 18 水合升级

  • 不再需要整页一次性水合
  • 选择性水合:先水合可见区域,其他组件等空闲再水合
  • Suspense + 流式渲染:HTML 分段来,JS 分段绑
  • 页面更早可交互,性能大幅提升

6. 水合不匹配错误 Hydration Mismatch
服务端 HTML 和客户端首次渲染结果不一致 → 报错。

常见原因:

  • 时间、随机数、浏览器环境变量不一致
  • 条件渲染用了 typeof window !== 'undefined'
  • 服务端、客户端数据不同

React 会强制修复,但会警告,影响性能。

总结
水合就是 SSR 场景下,React 在客户端对服务端生成的静态 HTML 绑定事件、建立组件树、初始化状态,让页面从“可看”变成“可交互”的过程。它不重新生成 DOM,只附加逻辑。React 18 进一步支持流式渲染与选择性水合,大幅提升交互体验。


7. 移除事件池(SyntheticEvent 池)

React 17 及以前
合成事件会被复用、放回对象池
异步访问 event 会报错,必须 e.persist()

React 18
事件池彻底移除

  • event 对象不再复用
  • 异步可直接访问
  • 不再需要 e.persist()
  • 代码更简单,bug 更少

8. 并发渲染(Concurrent Rendering) vs 异步渲染

最重要前置概念

  1. 并发渲染 ≠ 异步渲染;两者非常容易混淆,React官方明确区分。
  2. JS 始终单主线程,不存在真正并行多线程;
  3. 异步渲染是宽泛通用术语;并发渲染是 React 18 独有的、带抢占调度的渲染模型
  4. React渲染分为两大阶段:
    • Render阶段(协调、diff、构建fiber:可中断
    • Commit阶段(操作DOM、执行副作用:永远同步、不可中断

一、名词定义

异步渲染(通用概念,不是React专有名词)
含义:渲染工作不在当前同步调用栈立刻完成,放到后续事件循环执行。

  • 只有先后顺序,没有任务优先级抢占;
  • 一旦任务启动,无法暂停、不能被打断
  • 单纯依靠宏任务/微任务(setTimeout、Promise)延后执行渲染。

例子

// 异步触发更新,属于广义异步渲染
useEffect(() => {
  setTimeout(() => {
    setList(bigData); // 延后执行更新
  }, 0);
},[])

缺陷:
列表渲染阻塞主线程时,无法暂停;渲染一旦开始,必须一口气跑完,页面依然卡顿。

重点:异步 = 延后执行;不代表可中断、不支持优先级抢占。

并发渲染 Concurrent Rendering(React18 createRoot开启)
定义:具备抢占式调度能力的可中断渲染模型。
依托Fiber + Scheduler调度器 + 时间分片 + Lane优先级模型。

四大核心能力:

  1. 渲染任务拆分成微小工作单元(Fiber节点);
  2. 每执行一小段,判断是否时间片耗尽,主动让出主线程;
  3. 高优先级任务(输入、点击)可以中断正在执行的低优先级渲染
  4. 中断的任务可以恢复、甚至直接丢弃(过时更新)。

关键词:可中断、优先级抢占、能暂停/恢复/丢弃
开启条件:必须使用createRoot;老式ReactDOM.render是Legacy同步阻塞模式,不支持并发。

示例API(并发能力出口)

const [isPending, startTransition] = useTransition();
// 将更新标记为低优先级,可被交互抢占
startTransition(() => setList(largeData));

二、核心差异对照表

对比维度 异步渲染(广义) React并发渲染(Concurrent)
核心能力 仅延后执行任务 可中断、抢占式优先级调度
中断特性 任务启动后不可中断 渲染过程随时暂停、恢复、丢弃
优先级 无优先级,排队顺序执行 区分高低优先级;交互任务可插队
底层原理 浏览器事件循环(微任务/宏任务) Fiber链表 + Scheduler时间分片 + Lane优先级
能否解决长任务卡顿 ❌ 不能。只是延后卡顿,开始渲染依然阻塞 ✅ 可以,长时间渲染分片执行,不卡死UI
阶段限制 没有区分渲染阶段 仅Render协调阶段可中断;Commit提交DOM永远同步不可中断
典型代表 setTimeout内部setState、Promise回调更新 useTransition、useDeferredValue、createRoot模式
本质区别 改变执行时机 改变执行过程(能随时暂停)

通俗比喻

  • 异步渲染:食堂排队,排到你才开始做饭;一旦开火,不能停。
  • 并发渲染:厨师一边炒菜,一边留意客人呼叫;客人要点单(高优交互)立刻放下锅去接待,空闲了继续炒菜;如果客人需求变了,可以直接扔掉半成品重新做。

三、极易混淆的场景区分

场景1:
setTimeout里setState(异步渲染,不是并发

const handleClick = () => {
  setTimeout(() => {
    setBigList(hugeArr);
  },0)
}
  • 更新被延后执行;
  • 列表渲染一旦启动,无法中断;用户输入依然卡顿;
  • 哪怕在React18 createRoot环境,这只是「异步更新」,不具备并发抢占特性

场景2:
startTransition包裹setState(并发渲染)

const handleChange = (e) => {
  setText(e.target.value); // 高优先级:输入实时响应
  startTransition(() => {
    setSearchList(filterList(e.target.value)); // 低优,可中断
  })
}
  • 用户持续输入时,搜索列表渲染可以被持续打断
  • 输入框永远流畅,不会被大数据列表阻塞;
    这才是并发渲染独有的能力。

四、注意要点

要点一:并发渲染是不是多线程?
答:不是!依然运行在JS主线程。
并发 ≠ 并行。

  • 并行:多个线程同时干活(Web Worker)
  • React并发:同一个主线程,交替执行不同任务,协作调度,模拟并发效果。

要点二:
React18自动批处理 ≠ 并发渲染
自动批处理是独立优化;
只有createRoot开启并发模式,才能使用优先级调度、可中断渲染

五、总结

异步渲染是通用概念,指把渲染任务延后到后续事件循环执行,仅改变执行时机,任务一旦开始无法中断,没有优先级抢占。
并发渲染是React18独有的渲染模型,基于Fiber架构实现可中断渲染,依靠调度器做时间分片和优先级管理;高优先级用户交互可以中断低优先级渲染任务,保障界面流畅。

二者最大区别:异步只能延后执行;并发支持执行中途暂停、抢占、恢复。异步渲染解决“什么时候跑”,并发渲染解决“跑的过程能不能打断”。

拓展

问:既然并发Render阶段可中断,为什么Commit阶段不能中断?
答:为保证DOM一致性。如果DOM操作中途暂停,页面会渲染出残缺、不一致的UI;所以React必须一次性原子完成所有DOM变更。


React 18 核心特性极简总结

React 18 核心就是三件事:

  1. 并发渲染:可中断、可插队,保证 UI 流畅
  2. 自动批处理:所有场景 setState 合并,减少渲染
  3. 新 Hooks:useTransition、useDeferredValue 做优先级调度

同时配套:

  • createRoot 开启并发模式
  • 严格模式 Effect 执行两次帮你写健壮代码
  • SSR 流式渲染 + Suspense
  • 移除事件池,简化合成事件