React 18+ 深度笔记
本文聚焦 React 18,覆盖 Fiber 架构、Hooks 原理、并发特性、状态管理(zustand/RTK)、React 19 新特性等核心内容。
1. 核心概念
1.1 JSX
JSX 是 React.createElement 的语法糖,编译后生成 React Element(一个描述节点的普通对象):
// JSX
<div className="container">
<span>Hello</span>
</div>
// 编译后
React.createElement('div', { className: 'container' },
React.createElement('span', null, 'Hello')
)
// 结果(React Element)
{
$$typeof: Symbol(react.element),
type: 'div',
props: { className: 'container', children: { type: 'span', ... } },
key: null,
ref: null
}
$$typeof 用于防止 XSS 攻击 — JSON 无法序列化 Symbol,服务器返回的恶意 JSON 无法伪造 React 元素。
1.2 虚拟 DOM
React 的 Virtual DOM 与 Vue 的理念一致,但实现差异大:
- React 的虚拟 DOM 是一棵不可变的 React Element 树
- React 的 Diff 是在 Fiber 节点上进行的、可中断的过程
- React 使用 双缓冲(current tree + workInProgress tree)来支持中断恢复
1.3 Diff 算法(Reconciliation)
React 的 Diff 基于三个假设(策略):
- 树层级比较 — 只对同一层级的节点做 diff,跨层级移动视为销毁+重建(O(n^3) 降为 O(n))
- key 稳定假设 — 同 key 组件尽量复用
- 类型决定 — 节点类型变化(div → span)直接销毁重建子树
Diff 流程(Fiber 架构下):
- 比较新旧 fiber 节点的 key + type
- 匹配则打上 Update flag,复用 DOM
- 不匹配则标记 Placement(新增)或 Deletion(删除)
1.4 key 的作用
- 帮助 React 识别哪些节点发生了变化、被添加或移除
- 避免使用 index:增加/删除中间项会导致 key 错位,引发渲染错误和性能问题
- 组件 key 变化导致卸载后重新挂载(可用于强制重置组件状态)
2. Fiber 架构
2.1 为什么需要 Fiber
React 16 之前的递归 Diff(Stack Reconciler)不可中断,一旦开始必须执行完整个递归树。对于复杂组件树:
- 帧率问题:一次更新占用主线程过长(>16.6ms),导致掉帧
- 无法优先级调度:无法区分紧急(用户输入)和非紧急(数据加载)更新
Fiber 的核心目标:将渲染过程拆分为可中断的小单元,实现可中断、可复用、可优先级的异步渲染。
2.2 Fiber 节点结构
每个 React 元素都对应一个 Fiber 节点:
interface Fiber {
tag: WorkTag // 节点类型(FunctionComponent/HostComponent/...)
key: string | null
elementType: any
type: any // 函数组件本身、DOM 标签名等
stateNode: any // 关联的真实 DOM / 组件实例
// 链表结构
return: Fiber | null // 父节点
child: Fiber | null // 第一个子节点
sibling: Fiber | null // 下一个兄弟节点
// 双缓冲
alternate: Fiber | null // 对应 current 或 workInProgress 树的同级节点
// 副作用
flags: Flags // 操作标记(Placement/Update/Deletion/...)
subtreeFlags: Flags // 子树副作用标记(快速跳过无副作用的子树)
// 优先级
lanes: Lanes
childLanes: Lanes
// 更新队列
memoizedState: any // 当前状态(Hooks 链表头)
memoizedProps: any
updateQueue: any
// 副作用列表(已完成 render 阶段后收集)
firstEffect: Fiber | null
nextEffect: Fiber | null
lastEffect: Fiber | null
}
Fiber 用链表取代树:通过 return/child/sibling 三指针构成一棵单向链表树,使遍历可中断(暂停后从指针恢复)。
2.3 双缓冲树
current(当前屏幕显示) workInProgress(内存中构建)
FiberRoot FiberRoot
└── RootFiber ←—— alternate ——→ RootFiber
├── App (alternate) ←——→ App
├── Header (alt) ←——→ Header
└── ...
- current:当前已渲染在屏幕上的 Fiber 树
- workInProgress:正在构建中的替代树(内存中)
alternate连接两个树的对应节点- 构建完成后,
current指针切换为workInProgress(commitRoot时),旧树成为下一次的 workInProgress(复用 Fiber 对象)
2.4 工作循环(workLoop)
function workLoopConcurrent() {
while (workInProgress !== null && !shouldYield()) {
performUnitOfWork(workInProgress)
}
}
function shouldYield() {
// 检查是否超时(默认 5ms 时间片)
return deadlineDidExpire || navigator.scheduling?.isInputPending()
}
performUnitOfWork 分为两个阶段:
- beginWork(递):处理当前节点,diff + 生成子 fiber,返回 child
- 节点无更新且优先级不够 → bailout(跳过子树)
- 有更新 → 打 flag,继续深入
- completeUnitOfWork(归):构建副作用列表(effect list),收集 flags,返回 sibling
2.5 调度器(Scheduler)
React Scheduler 实现了一个基于优先级的任务调度系统:
- 使用
MessageChannel(宏任务)实现时间切片,避免阻塞事件队列 - 任务队列用小顶堆(min-heap)按到期时间排序
- 每次工作循环检查时间切片是否到期,到期则暂停,将控制权交还浏览器
调度优先级:
ImmediatePriority → 同步执行(过期后立即执行)
UserBlockingPriority → 用户交互(点击、输入)
NormalPriority → 常规更新
LowPriority → 低优先级数据加载
IdlePriority → 空闲时处理
2.6 时间切片(Time Slicing)
- 每次
requestAnimationFrame回调获取frameLength(默认 5ms) - Scheduler 在这个时间片内执行 Fiber 工作单元
- 超时则挂起当前工作,将任务放回队列,让浏览器处理事件和渲染
- 下次空闲继续上次中断的 fiber
2.7 Lane 模型(优先级)
React 18 用 32 位二进制的 lanes 替代了之前的 expirationTime:
NoLanes = 0b000...000
SyncLane = 0b000...001
InputContinuousLane = 0b000...010
DefaultLane = 0b000...100
TransitionLane = 0b000...1000
IdleLane = 0b111...111 (高位)
- 位数越低优先级越高
- 位运算快速判断优先级关系和集合操作
- 一个更新可能同时属于多个 lane(如 transition 更新)
3. Hooks
3.1 useState
const [state, setState] = useState(initialValue)
批量更新(Batching):
- React 18 之前在事件处理函数中自动批处理;setTimeout/Promise 回调中不会
- React 18 所有更新都会自动批处理(包括 setTimeout、Promise、原生事件)
setCount(c => c + 1) // 函数式更新:基于旧值计算,能拿到最新状态
setCount(count + 1) // 直接传值:可能受闭包影响拿到过期值
3.2 useEffect
挂载: → effect 函数执行
更新: → 清理函数执行(基于旧值) → effect 函数执行(新值)
卸载: → 清理函数执行
useEffect(() => {
// effect
return () => { /* 清理 */ }
}, [dep1, dep2])
- 在浏览器完成布局和绘制之后异步执行(延迟执行,不阻塞渲染)
- 依赖数组执行
Object.is浅比较(引用比较) - 空依赖
[]只在挂载时执行一次 - 不传依赖时每次 render 后都执行
与生命周期的关系:useEffect 模拟 componentDidMount + componentDidUpdate + componentWillUnmount,但语义不同:它关注同步而非生命周期阶段。
3.3 useLayoutEffect
与 useEffect 完全相同的签名,但执行时机不同:
| useEffect | useLayoutEffect | |
|---|---|---|
| 执行时机 | 浏览器绘制之后(异步) | DOM 更新后、绘制之前(同步) |
| 阻塞绘制 | 否 | 是 |
| 适用场景 | 副作用、数据请求、日志 | DOM 测量、同步修改布局(避免闪烁) |
原则:能用 useEffect 就不用 useLayoutEffect,因为它会阻塞视觉更新。
3.4 useRef
const ref = useRef(initialValue)
// ref.current 可读写,不引发重新渲染
- 保存可变值的"盒子",
ref.current在每次渲染间保持不变 - 用于访问 DOM(
<div ref={ref}>)或存储任意可变值(定时器 ID、前一次值等) useRef(null)的实现:{ current: null },React 保证在组件整个生命周期中返回同一对象引用
3.5 useMemo / useCallback
const memoizedValue = useMemo(() => expensive(a, b), [a, b])
const memoizedFn = useCallback(() => doSomething(a, b), [a, b])
// 等价于 useMemo(() => fn, deps)
缓存原理:
- 首次渲染执行函数,缓存结果
- 依赖变化时重新计算,覆盖缓存
- 依赖不变则直接返回缓存引用
不要滥用:
useMemo本身有计算成本(依赖比较 + 内存占用)- 只有昂贵计算或作为引用稳定性场景(传递给
React.memo子组件)才有意义 - 过早优化不如不优化 — 优先分析性能瓶颈
3.6 useReducer
适合复杂状态逻辑(多值、状态间依赖):
const [state, dispatch] = useReducer(reducer, initialState, init?)
内部实现复用 useState:将 updater 替换为 dispatch,在 reducer 中计算 newState。
3.7 useContext / use(React 19)
const theme = useContext(ThemeContext)
use(React 19 实验性):在 render 中直接读取 Promise 或 Context,支持条件性使用:
const data = use(fetchData()) // 读取 Promise(配合 Suspense)
const theme = use(ThemeContext) // 替代 useContext
与 useContext 区别:use 可在条件分支和循环中使用,useContext 必须在顶层。
3.8 自定义 Hook 设计
自定义 Hook 是以 use 开头的函数,通过组合内置 Hook 实现逻辑复用:
function useWindowSize() {
const [size, setSize] = useState({ width: 0, height: 0 })
useEffect(() => {
const handler = () => setSize({ width: window.innerWidth, height: window.innerHeight })
window.addEventListener('resize', handler)
return () => window.removeEventListener('resize', handler)
}, [])
return size
}
设计原则:
- Hook 之间是独立的 闭包隔离,不是 Mixin 的合并
- 多个自定义 Hook 可组合(Hook 调用 Hook)
- 以状态和副作用为核心抽象,非生命周期
4. 并发特性(React 18)
4.1 并发模式(Concurrent Mode)
并发模式不是某个 API,而是 React 18 的底层渲染架构变化。核心价值:
- 可中断渲染:高优更新可以打断低优更新
- 过渡更新不阻塞 UI:UI 更新期间仍可响应输入
- 状态复用:中断的渲染状态可被丢弃,直接回到旧状态
启用并发模式需要 createRoot(替代 ReactDOM.render):
// React 18
const root = createRoot(container)
root.render(<App />)
4.2 useTransition
标记非紧急更新,让 React 为低优更新降级优先级:
function SearchPage() {
const [query, setQuery] = useState('')
const [isPending, startTransition] = useTransition()
return (
<>
<input value={query} onChange={e => {
setQuery(e.target.value) // 紧急:更新输入框
startTransition(() => {
setSearchResults(filter(e.target.value)) // 非紧急:过滤结果
})
}} />
{isPending && <Spinner />}
</>
)
}
应用场景:大量列表过滤、搜索建议、标签切换。
4.3 useDeferredValue
与 useTransition 类似,但用于父组件无法控制更新的场景:
const deferredQuery = useDeferredValue(query)
// deferredQuery 延迟更新,在紧急渲染完成后才更新
const suggestions = useMemo(() => filter(list, deferredQuery), [list, deferredQuery])
区别:
useTransition:包装状态更新逻辑(函数)useDeferredValue:包装状态值(值)
4.4 自动批处理(Automatic Batching)
React 18 中所有更新自动批处理(不再区分事件内外):
// React 17:setTimeout 中触发两次渲染
// React 18:只触发一次渲染
setTimeout(() => {
setCount(c => c + 1)
setFlag(f => !f)
}, 100)
如需取消批处理可用 flushSync:
import { flushSync } from 'react-dom'
flushSync(() => setCount(c => c + 1)) // 立即渲染
flushSync(() => setFlag(f => !f)) // 再次渲染
4.5 Suspense(数据获取 / 代码分割 / 流式 SSR)
// 代码分割
const LazyComp = React.lazy(() => import('./LazyComp'))
<Suspense fallback={<Loading />}>
<LazyComp />
</Suspense>
// 数据获取(配合 Relay / SWR / React 19 use())
<Suspense fallback={<Skeleton />}>
<UserProfile userId={id} />
</Suspense>
在 Concurrent 模式下,Suspense 可以「暂停」渲染,不卸载原有内容,只需展示 fallback。
5. 状态管理
5.1 Context 的性能问题与优化
Context 的核心问题:所有消费该 Context 的组件都会在 Context 值变化时重新渲染,即使子组件只读取了其中一部分数据。
const AppContext = createContext({ user, theme })
// ❌ 问题:theme 变化导致只用了 user 的组件也重新渲染
解决方案:
- 拆分 Context:
const UserContext = createContext(user)
const ThemeContext = createContext(theme)
- 使用 useMemo:避免 provider value 每次重渲染生成新引用
const value = useMemo(() => ({ user, theme }), [user, theme])
- use-context-selector(第三方库):通过订阅选择器避免不必要的重渲染
5.2 zustand 原理
Zustand 的核心实现 = 发布订阅模式 + 不可变更新:
// 简化版原理
function createStore(createState) {
let state = createState(set, get)
const listeners = new Set()
const set = (partial) => {
state = Object.assign({}, state, typeof partial === 'function'
? partial(state) : partial)
listeners.forEach(l => l(state))
}
const get = () => state
const subscribe = (listener) => {
listeners.add(listener)
return () => listeners.delete(listener)
}
const api = { setState: set, getState: get, subscribe }
return api
}
选择器 + 浅比较:
const bears = useBearStore(s => s.bears)
// 默认使用 Object.is 比较,支持 shallow 中间件
5.3 Redux Toolkit (RTK)
createSlice:自动生成 reducer 和 actions,内置 immer 简化不可变更新:
const slice = createSlice({
name: 'counter',
initialState: { value: 0, user: null },
reducers: {
increment: (state) => { state.value += 1 }, // immer 代理
setUser: (state, action) => { state.user = action.payload }
}
})
createAsyncThunk:管理异步请求的 pending/fulfilled/rejected 三态:
const fetchUser = createAsyncThunk('user/fetch', async (id, { rejectWithValue }) => {
const res = await api.getUser(id)
if (!res.ok) return rejectWithValue(res.error)
return res.data
})
// builder.addCase(fetchUser.fulfilled, ...)
RTK Query:缓存策略、自动失效、乐观更新:
const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
endpoints: (builder) => ({
getPosts: builder.query({ query: () => 'posts' }),
addPost: builder.mutation({
query: (body) => ({ url: 'posts', method: 'POST', body }),
invalidatesTags: ['Posts']
})
})
})
5.4 总结 zustand vs RTK vs Context
| 方案 | 适用场景 | 学习成本 | 性能 |
|---|---|---|---|
| Context | 低频全局数据(主题、语言) | 低 | 差(全部重渲染) |
| zustand | 中小型应用、单页组件数据 | 低 | 优(选择器订阅) |
| RTK | 大型应用、复杂异步逻辑 | 中高 | 优 |
6. React 19 新特性
6.1 Server Components (RSC)
服务端组件仅在服务端运行,不发送 JavaScript 到客户端:
- 减少包体积:大型依赖仅在服务端执行
- 直接访问后端资源:数据库、文件系统、内部 API
- 自动代码分割:客户端组件边界就是分割点
RSC 与 SSR 的区别:
| RSC | SSR | |
|---|---|---|
| 渲染时机 | 构建时或请求时 | 请求时 |
| 产物 | 序列化的 UI 描述(不是 HTML) | HTML 字符串 |
| 交互性 | 不可交互(无 useState/useEffect) | 可交互(hydrate 后) |
| 重新获取 | 可重新请求组件树 | 需刷新整页 |
| 前后端思维 | 组件即前后端统一模型 | 服务端渲染 + 客户端水合 |
6.2 Server Actions
客户端可通过表单或事件调用服务端函数:
// server action(RSC 中标记)
async function submitForm(formData: FormData) {
'use server'
await saveToDB(formData)
}
// 客户端组件调用
<form action={submitForm}>...</form>
6.3 useOptimistic
乐观更新 UI(先显示预期结果,后端确认后校正):
const [optimisticMessages, addOptimistic] = useOptimistic(messages)
6.4 useFormStatus
获取表单提交状态(常用于子组件感知父表单状态):
const { pending, data } = useFormStatus()
7. 性能优化
7.1 React.memo(浅比较)
const Child = React.memo(({ data }: { data: Item }) => {
return <ExpensiveComponent data={data} />
})
- 默认对 props 做浅比较(Object.is 逐个字段比较)
- 父组件重渲染但 props 未变 → 跳过子组件渲染
- 配合 useMemo/useCallback 传递引用稳定的 props 才有意义
7.2 useMemo / useCallback 的正确场景
✅ 应该用:
- 传递给
React.memo子组件的 props(保持引用稳定) - 昂贵的计算(大量数据过滤/格式化)
- 作为其他 Hook 的依赖
❌ 不应该用:
- 所有函数/计算都包一层(增加比较成本)
- 简单计算(不如直接用)
- 没有
React.memo子组件的回调函数
7.3 虚拟列表(react-window / react-virtuoso)
import { FixedSizeList } from 'react-window'
<FixedSizeList height={600} itemCount={10000} itemSize={50}>
{({ index, style }) => <div style={style}>Item {index}</div>}
</FixedSizeList>
原理:计算可见范围 → 只渲染可见行 → 通过 padding-top/padding-bottom 撑开滚动高度。
7.4 代码分割(React.lazy + Suspense)
const Dashboard = React.lazy(() => import('./Dashboard'))
配合路由级分割效果最佳:
const routes = [
{ path: '/dashboard', element: <Suspense><Dashboard /></Suspense> }
]
7.5 避免内联函数/对象
内联函数/对象每次渲染创建新的引用,破坏 React.memo 的优化效果:
// ❌ 每次渲染创建新函数
<ExpensiveChild onClick={() => handleClick(id)} />
// ✅ useCallback 保持引用稳定
const handleClick = useCallback(() => { /* ... */ }, [id])
<ExpensiveChild onClick={handleClick} />
7.6 Profiler 分析
<Profiler id="Navigation" onRender={(id, phase, actualDuration) => {
console.log({ id, phase, actualDuration })
}}>
<Navigation />
</Profiler>
用于定位渲染瓶颈,分析 Commit 耗时和触发原因。
8. React Router v6
8.1 createBrowserRouter
const router = createBrowserRouter([
{
path: '/',
element: <Root />,
errorElement: <ErrorPage />,
children: [
{ index: true, element: <Home /> },
{ path: 'users/:id', element: <User /> },
]
}
])
8.2 Outlet
function Root() {
return (
<>
<Header />
<main><Outlet /></main> {/* 子路由渲染位置 */}
<Footer />
</>
)
}
8.3 loader / action
const route = {
path: 'users/:id',
loader: ({ params }) => fetch(`/api/users/${params.id}`),
element: <User />,
action: async ({ request }) => {
const formData = await request.formData()
return updateUser(formData)
}
}
// 组件中获取
function User() {
const data = useLoaderData() // loader 返回的数据
const navigation = useNavigation() // 加载状态
const fetcher = useFetcher() // 非导航请求
return <fetcher.Form method="post">...</fetcher.Form>
}
8.4 懒加载
const UserRoute = {
path: 'users',
async lazy() {
const { Users } = await import('./Users')
return { Component: Users, loader: () => fetch('/api/users') }
}
}