React面试宝典【Part4:React 18 核心特性】
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 才会批量更新
在 setTimeout、Promise、原生事件里的 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 流程
- 服务端渲染 React 组件 → 生成纯静态 HTML
- 返回给浏览器 → 页面立刻显示内容(快)
- 浏览器加载 JS(React 代码)
- React 把事件、状态、逻辑绑到已有 DOM 上 → 这一步就是 Hydrate 水合
没有水合:
- 页面能看
- 按钮点不动
- 输入框输不了
- 完全是死页面
有水合:
- 页面可交互
- 有 state
- 有事件
- 变成正常 React 应用
2. 水合做了什么?
不重新创建 DOM
复用服务端已经渲染好的 HTML,不重新渲染。绑定事件监听
onClick、onChange、合成事件系统。建立组件树、Fiber 树
让 React 知道 DOM 对应哪个组件。初始化 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 异步渲染
最重要前置概念
- 并发渲染 ≠ 异步渲染;两者非常容易混淆,React官方明确区分。
- JS 始终单主线程,不存在真正并行多线程;
- 异步渲染是宽泛通用术语;并发渲染是 React 18 独有的、带抢占调度的渲染模型。
- React渲染分为两大阶段:
- Render阶段(协调、diff、构建fiber:可中断)
- Commit阶段(操作DOM、执行副作用:永远同步、不可中断)
一、名词定义
异步渲染(通用概念,不是React专有名词)
含义:渲染工作不在当前同步调用栈立刻完成,放到后续事件循环执行。
- 只有先后顺序,没有任务优先级抢占;
- 一旦任务启动,无法暂停、不能被打断;
- 单纯依靠宏任务/微任务(setTimeout、Promise)延后执行渲染。
例子
// 异步触发更新,属于广义异步渲染
useEffect(() => {
setTimeout(() => {
setList(bigData); // 延后执行更新
}, 0);
},[])
缺陷:
列表渲染阻塞主线程时,无法暂停;渲染一旦开始,必须一口气跑完,页面依然卡顿。
重点:异步 = 延后执行;不代表可中断、不支持优先级抢占。
并发渲染 Concurrent Rendering(React18 createRoot开启)
定义:具备抢占式调度能力的可中断渲染模型。
依托Fiber + Scheduler调度器 + 时间分片 + Lane优先级模型。
四大核心能力:
- 渲染任务拆分成微小工作单元(Fiber节点);
- 每执行一小段,判断是否时间片耗尽,主动让出主线程;
- 高优先级任务(输入、点击)可以中断正在执行的低优先级渲染;
- 中断的任务可以恢复、甚至直接丢弃(过时更新)。
关键词:可中断、优先级抢占、能暂停/恢复/丢弃
开启条件:必须使用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 核心就是三件事:
- 并发渲染:可中断、可插队,保证 UI 流畅
- 自动批处理:所有场景 setState 合并,减少渲染
- 新 Hooks:useTransition、useDeferredValue 做优先级调度
同时配套:
- createRoot 开启并发模式
- 严格模式 Effect 执行两次帮你写健壮代码
- SSR 流式渲染 + Suspense
- 移除事件池,简化合成事件