目录
- 前言
- 一、性能优化常识
-
- 1、性能优化的基本原理(为什么会慢)
- 2、先测量:定位问题的工具与指标
-
- [(1)、核心指标(Web Vitals)](#(1)、核心指标(Web Vitals))
- (2)、测量工具
- [二、React 项目性能优化(全面汇总)](#二、React 项目性能优化(全面汇总))
-
- 1、常见的性能优化(⭐️⭐️⭐️⭐️⭐️)
-
- (1)、避免不必要的重新渲染(最核心)(⭐️)
- (2)、列表渲染优化(⭐️)
- [(3)、useCallback 正确使用(⭐️)](#(3)、useCallback 正确使用(⭐️))
- [(4)、useMemo 注意事项(⭐️)](#(4)、useMemo 注意事项(⭐️))
- [(5)、使用 "React.memo + key 策略" 优化子组件](#(5)、使用 “React.memo + key 策略” 优化子组件)
-
- [①、React.memo 的深度优化](#①、React.memo 的深度优化)
- [②、key 属性的策略性使用](#②、key 属性的策略性使用)
- ③、组合使用场景
- (6)、避免在渲染期间创建函数、对象、数组
- [(7)、正确管理状态位置(State colocation)](#(7)、正确管理状态位置(State colocation))
- [(8)、拆分组件(component splitting)](#(8)、拆分组件(component splitting))
- [(9)、避免在 useEffect 中做耗时操作](#(9)、避免在 useEffect 中做耗时操作)
- [(10)、使用 lazy() + Suspense 拆分 bundle](#(10)、使用 lazy() + Suspense 拆分 bundle)
- (11)、使用虚拟列表优化长列表
- [(12)、Stale closures(闭包陷阱)与 useRef 解决](#(12)、Stale closures(闭包陷阱)与 useRef 解决)
- [(13)、useSyncExternalStore 小示例(自定义外部 store)](#(13)、useSyncExternalStore 小示例(自定义外部 store))
- 2、中级优化(适用于中大型项目)
-
- [(1)、使用 React Query / SWR](#(1)、使用 React Query / SWR)
- [(2)、使用 useTransition 避免卡顿](#(2)、使用 useTransition 避免卡顿)
- [(3)、useDeferredValue 让渲染更流畅](#(3)、useDeferredValue 让渲染更流畅)
- [(4)、使用 memoized selector](#(4)、使用 memoized selector)
- [(5)、事件委托 + useEvent(React 19)(⭐️)](#(5)、事件委托 + useEvent(React 19)(⭐️))
- 3、进阶优化("更高级但常被忽视")
-
- [(1)、RSC(React Server Components)](#(1)、RSC(React Server Components))
- [(2)、懒加载 + 分包 + 预加载技术](#(2)、懒加载 + 分包 + 预加载技术)
- [(3)、避免不必要的 context 传递](#(3)、避免不必要的 context 传递)
- [(4)、避免在 render 中做逻辑判断](#(4)、避免在 render 中做逻辑判断)
- (5)、使用生产环境构建
- 4、总结(面试最标准答案)(⭐️⭐️⭐️)
- [三、React 项目性能优化(定点爆破)](#三、React 项目性能优化(定点爆破))
-
- [1、首屏(Initial load / LCP)优化(⭐️⭐️⭐️)](#1、首屏(Initial load / LCP)优化(⭐️⭐️⭐️))
-
- [(1)、削减 JS 初始体积](#(1)、削减 JS 初始体积)
- [(2)、代码分割(Route + Component)](#(2)、代码分割(Route + Component))
- [(3)、Server Components / SSR / Streaming](#(3)、Server Components / SSR / Streaming)
- [(4)、图片 & 字体优化](#(4)、图片 & 字体优化)
- [(5)、Critical CSS](#(5)、Critical CSS)
- [2、交互流畅(输入/动画/滚动 --- FID/INP)](#2、交互流畅(输入/动画/滚动 — FID/INP))
- 3、避免不必要的重渲染(核心是"减少渲染次数与大小")(⭐️⭐️⭐️)
- [4、大列表优化(virtualization & windowing)(⭐️⭐️⭐️)](#4、大列表优化(virtualization & windowing)(⭐️⭐️⭐️))
- 5、数据层优化(服务器状态与缓存)
- 6、渲染与布局(CSS、动画、paint)
- [7、Hydration 与 RSC 相关实践](#7、Hydration 与 RSC 相关实践)
- [8、构建与交付优化(Bundle / Network)](#8、构建与交付优化(Bundle / Network))
- 四、架构级建议(中大型项目)
前言
先测量:用 Web Vitals / Profiler 定位问题(LCP、FID/INP、CLS)。
按成本优先级修复:首屏/交互流畅 > 长列表渲染 > JS bundle 体积 > 问题微调。
组合手段:Server Components / SSR + Suspense + 缓存(React Query)+ 并发特性(useTransition)+ 精细化局部更新。
一、性能优化常识
1、性能优化的基本原理(为什么会慢)
- 渲染耗时:组件渲染/调和(reconciliation)+ commit(DOM 更新)耗时。
- JS 主线程阻塞:解析/执行/垃圾回收会阻塞 UI。
- 网络与资源加载:大 bundle、图片、字体、API 延迟影响首屏(LCP)与交互感受。
- 不必要重渲染:粗粒度状态更新 / 不稳定 props / 无谓创建函数/对象 会触发大量重新渲染。
- Hydration 成本:客户端需要重新绑定事件、补全 DOM,hydration 太重会阻塞交互。
理解这几点就能对症下药。
2、先测量:定位问题的工具与指标
(1)、核心指标(Web Vitals)
- LCP(Largest Contentful Paint) --- 首屏感知;
- FID / INP(交互延迟) --- 输入响应;
- CLS(布局偏移) --- 视觉稳定性。
(2)、测量工具
- Lighthouse / PageSpeed Insights(高层问题)
- Web Vitals(in-page 实时采集)
- Chrome DevTools Performance / CPU profiling(JS 执行)
- React DevTools Profiler(组件渲染时间、渲染次数)
- Profiler API ()
- bundle analyzers(webpack-bundle-analyzer / rollup-plugin-visualizer / Vite plugin)
- Trace Event、Performance.mark / measure(自定义标记)
实践:先跑 Lighthouse,找到首屏大块时间来源(JS、图片、字体、TTFB);再用 React Profiler 看哪些组件 heavy。
二、React 项目性能优化(全面汇总)
在优化之前必须了解现代 React 专用关键技术栈(趋势与组合):
服务端优先:RSC(Server Components)+ Streaming SSR → 减少客户端 JS。
并发特性:useTransition / useDeferredValue,优先保持交互流畅。
数据缓存层:React Query / RTK Query / SWR 做服务器状态缓存、乐观更新、并发请求合并。
按需加载:React.lazy + Suspense + route-based code-splitting。
细粒度本地状态:Zustand / Jotai(按订阅更新),避免 Context 粗粒度重渲染。
虚拟化:react-window / react-virtual / TanStack Virtual 用于大列表。
构建器 :Vite(dev), Turbopack/ESBuild(下一代)。
这些是当前最有效的组合。
1、常见的性能优化(⭐️⭐️⭐️⭐️⭐️)
(1)、避免不必要的重新渲染(最核心)(⭐️)
React 的最大性能瓶颈就是 组件重新渲染次数过多。
最常见的导致重复渲染的写法:
cpp
function Parent() {
const obj = { a: 1 }; // 每次 render 都是新对象
return <Child data={obj} />;
}
优化:
- useMemo() 保持稳定引用
- useCallback() 保持稳定函数
- 使用 React.memo() 避免子组件重复渲染
(2)、列表渲染优化(⭐️)
列表是 React 项目的最大渲染压力源之一。
错误 key:
cpp
items.map((item, index) => <Item key={index} />)
正确 key:
cpp
items.map(item => <Item key={item.id} />)
进阶优化:
- 虚拟列表(react-virtual / react-window)
- 分页加载(无限滚动)
- 避免列表元素嵌套太深
(3)、useCallback 正确使用(⭐️)
仅在满足以下情况使用:
- 子组件依赖 props 且使用 React.memo
- 计算量大的运算(如过滤、排序)
- 不稳定引用导致重渲染
误用(无意义):
cpp
function Comp() {
const handle = useCallback(() => setX(x + 1), [x]);
return <Child onClick={handle} />;
}
如果 Child 本身没有 memo,则没有意义。
正确:当 Child 是 memo 且依赖稳定:
cpp
const Child = React.memo(({onClick}) => <button onClick={onClick}>OK</button>);
function Parent() {
const handle = useCallback(() => setX(x => x + 1), []);
return <Child onClick={handle} />;
}
(4)、useMemo 注意事项(⭐️)
- 别把行为逻辑放入 useMemo。因为:
- useMemo不是保证缓存,只是优化提示(hint)------React 可能会在某些情况下选择丢弃或重新计算缓存值。
- 只有当计算昂贵时才用 useMemo。
适用场景:
- 计算成本高的操作:当有复杂计算或数据转换操作时。例如:
cpp
const sortedList = useMemo(() => {
return largeArray.sort(complexSortFn);
}, [largeArray]);
- 引用类型依赖:防止子组件不必要渲染。例如:
cpp
const config = useMemo(() => ({
mode: 'advanced',
threshold: 0.8
}), []);
不适用场景:
- 简单计算(可能反而会增加性能开销)
- 保证数据持久化的场景(应考虑使用 state 或上下文)
- 需要精确控制缓存生命周期的场景
(5)、使用 "React.memo + key 策略" 优化子组件
- 不要滥用 memo,简单的组件可能反而会降低性能
- 确保 key 的稳定性,避免使用随机数或时间戳
- 对于函数类型的 props,考虑使用 useCallback 配合
①、React.memo 的深度优化
作用原理:通过浅比较 props 来避免不必要的渲染。
使用方式:
cpp
const Button = React.memo(function Button({ onClick, label }) {
console.log('Button rendered:', label);
return <button onClick={onClick}>{label}</button>;
});
适用场景:
- 纯展示型组件
- 渲染开销较大的复杂组件
- 频繁接收相同 props 的组件
避免:
- 子组件内部有 useEffect 依赖 props
- 子组件 props 变化频繁
②、key 属性的策略性使用
核心作用:帮助 React 识别列表中哪些元素发生了变化
最佳实践:
- 使用稳定唯一标识(如数据 ID)而非数组索引
- 在动态列表中添加 key 可以显著提升 diff 算法效率,例如:
cpp
{items.map(item => (
<Button
key={item.id} // 使用稳定的唯一标识
/>
))}
③、组合使用场景
- 列表渲染优化:当渲染大型列表时,同时使用 memo 和 key
- 状态更新优化:父组件状态变化时,未变化的子组件不会重新渲染
- 性能对比:比单纯使用 PureComponent 更灵活可控
例如:
cpp
function Parent({ list }) {
return (
<>
{list.map((item) => (
<Button
key={item.id} // ✔ 稳定 key
onClick={item.onClick} // ❗可能不稳定
label={item.label}
/>
))}
</>
);
}
(6)、避免在渲染期间创建函数、对象、数组
常见错误:
cpp
<Modal style={{ width: 100 }} />
<Button onClick={() => doSomething()} />
优化:
cpp
const style = useMemo(() => ({ width: 100 }), []);
const handleClick = useCallback(() => doSomething(), []);
(7)、正确管理状态位置(State colocation)
React 状态越靠上层 → 重渲染树越大 → 性能越差
🚫 错误:把所有状态定义在根组件
✅ 正确:状态放在最小必要的组件中
(8)、拆分组件(component splitting)
大型组件 = 重新渲染范围大
拆成多个小组件:
- 更容易用 React.memo 包装
- 渲染颗粒度更细
- 逻辑更清晰
(9)、避免在 useEffect 中做耗时操作
常见错误:
cpp
useEffect(() => {
heavyCompute()
}, [])
应当:
- 搬到 Web Worker
- 搬到 useMemo
- 后端处理
(10)、使用 lazy() + Suspense 拆分 bundle
减少首屏体积:
cpp
const Chart = React.lazy(() => import('./Chart'));
配合:
cpp
<Suspense fallback={<Loading />}>
<Chart />
</Suspense>
(11)、使用虚拟列表优化长列表
React 的渲染能力有限,一次渲染上千行 DOM 会卡。
解决方案:
- react-window(最稳定)
- react-virtual(TanStack 官方)
- react-virtuoso(高质量,含大量功能)
(12)、Stale closures(闭包陷阱)与 useRef 解决
问题:
cpp
useEffect(() => {
const id = setInterval(() => console.log(count), 1000);
return () => clearInterval(id);
}, []); // 仅日志初始计数
解决:用 ref
cpp
const countRef = useRef(count);
useEffect(() => { countRef.current = count; });
useEffect(() => {
const id = setInterval(() => console.log(countRef.current), 1000);
return ()=>clearInterval(id);
}, []);
(13)、useSyncExternalStore 小示例(自定义外部 store)
cpp
function createStore() {
let state = 0;
const listeners = new Set();
return {
get: () => state,
set: (v) => { state = v; listeners.forEach(l => l()); },
subscribe: (l) => { listeners.add(l); return () => listeners.delete(l); }
}
}
const store = createStore();
function useCount() {
return useSyncExternalStore(store.subscribe, store.get);
}
2、中级优化(适用于中大型项目)
(1)、使用 React Query / SWR
它们能减少:
- 重复请求
- 缓存失效渲染
- loading 状态更新造成的渲染抖动
React Query 特别适合大量接口的复杂项目。
(2)、使用 useTransition 避免卡顿
适合输入框搜索、过滤、排序:
cpp
const [isPending, startTransition] = useTransition();
startTransition(() => {
setFilter(keyword);
});
能避免 UI 直接卡住。
(3)、useDeferredValue 让渲染更流畅
cpp
const deferredValue = useDeferredValue(searchValue);
用于大组件渲染,与用户输入解耦。
(4)、使用 memoized selector
如果使用 Redux:
cpp
const selectUser = createSelector([state => state.user], user => user)
能显著减少组件渲染。
(5)、事件委托 + useEvent(React 19)(⭐️)
React 19 新 Hook:
cpp
const onClick = useEvent(onClickHandler)
能让事件回调稳定,不再触发刷新。
3、进阶优化("更高级但常被忽视")
(1)、RSC(React Server Components)
React 未来核心方向:
- 更少的客户端 JS
- 更快的首屏渲染
- 更小的 bundle
- 清除大量 useEffect
如果你使用 Next.js 14+,你已经在用。
(2)、懒加载 + 分包 + 预加载技术
- React.lazy
- dynamic import
- preload / prefetch(Next.js 自动处理)
(3)、避免不必要的 context 传递
Context 一旦变,所有子组件都刷新。
优化:
- 拆分 context
- 使用 selector context(像 Zustand)
- 将 UI 状态放入组件,不放在 context 里
(4)、避免在 render 中做逻辑判断
最坏案例:
cpp
<select>
{options.map(...) /* 大计算 */}
</select>
优化:
cpp
const renderedOptions = useMemo(() => options.map(...), [options]);
(5)、使用生产环境构建
确保 Webpack/Vite 启用:
- tree-shaking
- minify
- terser
- dead code elimination
4、总结(面试最标准答案)(⭐️⭐️⭐️)
React 项目最常见的性能优化包括:
- 避免不必要的渲染(React.memo、useMemo、useCallback)
- 优化列表渲染
- 状态下沉(减少重渲染范围)
- 拆分组件、懒加载
- 避免在 render/useEffect 中做重运算
- 采用虚拟列表
- 使用 React Query / 状态选择器减少刷新
- 使用 useTransition / useDeferredValue 优化交互
- 在大型项目中使用 RSC 提升首屏性能
三、React 项目性能优化(定点爆破)
1、首屏(Initial load / LCP)优化(⭐️⭐️⭐️)
(1)、削减 JS 初始体积
- 分析 bundle,移除 polyfills 多余部分,开启 tree-shaking、sideEffects。
- 把大依赖拆到异步加载或服务器端执行(例如数据处理、渲染逻辑)。
- 使用 ES modules + HTTP/2 或 HTTP/3 + CDN。
(2)、代码分割(Route + Component)
cpp
const Page = lazy(() => import('./HeavyPage'));
<Suspense fallback={<Spinner/>}><Page/></Suspense>
- 优先在路由边界拆分;把管理控制面板的 heavy libs 异步。
(3)、Server Components / SSR / Streaming
- 把可在 server 渲染的部分移到 RSC,减少客户端 JS。
- 使用流式渲染(Next.js / custom server)降低 TTFB 后首屏出现时间。
(4)、图片 & 字体优化
- 使用 responsive images ()、AVIF/WebP、placeholder LQIP 或 blur-up。
- preload LCP 关键资源;font-display: swap。
- 使用 CDN + cache headers。
(5)、Critical CSS
- 抽取关键样式 inline,延迟非关键 CSS。
2、交互流畅(输入/动画/滚动 --- FID/INP)
- 并发特性
cpp
const [isPending, startTransition] = useTransition();
function onChange(e) {
setInputValue(e.target.value); // 紧急
startTransition(() => setFilter(e.target.value)); // 非紧急
}
- 把非关键更新放入 startTransition,保证输入响应。
- useDeferredValue
- 当你有昂贵计算基于输入时:const deferred = useDeferredValue(value); 然后在计算中用 deferred,保持输入流畅。
- 避免长任务
- 把 CPU 密集型计算放到 Web Worker(尤其数据可视化),或拆分为 requestIdleCallback / requestAnimationFrame 批处理。
- 节流/防抖
- 对滚动、resize、输入搜索使用 listenerApi.delay/debounce 脚本或 Listener Middleware。
3、避免不必要的重渲染(核心是"减少渲染次数与大小")(⭐️⭐️⭐️)
- 状态设计:局部化 & 精细订阅
- 小状态放在本组件或子组件;避免把大对象放入 Context。
- 使用 Zustand、Jotai、useSyncExternalStore 等做按需订阅。
- 组件拆分
- 把大组件拆成小组件,减少每次状态改变要重新渲染的树范围。
- React.memo 与 selectors
- React.memo + stable props:避免无谓渲染。
- 使用 reselect / createSelector 做计算缓存。
- useCallback/useMemo 的正确使用
- 只在作为 props 传递给 memoized 子组件时使用 useCallback。
- useMemo 用于昂贵计算,避免过度使用(创建缓存也有成本)。
常见正确范式:
cpp
const handleClick = useCallback(() => doThing(id), [id]);
const filtered = useMemo(() => expensiveFilter(list), [list]);
- 避免创建不稳定引用
cpp
// ❌ 不要在 render 里每次创建新对象传 child:
<Child style={{color: 'red'}} />
// ✅ 抽出常量或用 useMemo。
- immutable 更新 + immer
- 使用 Immer(RTK)便捷写法,但确保 reducer 不产生无谓替换。
4、大列表优化(virtualization & windowing)(⭐️⭐️⭐️)
- 推荐使用 react-window / react-virtual 做大列表,基本示例:
cpp
<FixedSizeList height={600} itemSize={50} itemCount={items.length}>
{({index, style}) => <Row index={index} style={style} />}
</FixedSizeList>
- 动态高度:使用 VariableSizeList 或测量缓存(react-virtual 提供更好支持)。
- 避免重建 item renderer(稳定 key 与 props)。
- 复用 DOM 节点 & shouldComponentUpdate / memo。
5、数据层优化(服务器状态与缓存)
- React Query / RTK Query:自动缓存、并发请求合并、后台刷新、乐观更新。
- 使用 staleTime、cacheTime 做粒度控制。
- 乐观更新模式:
cpp
onMutate: async (newTodo) => {
await queryClient.cancelQueries(['todos']);
const prev = queryClient.getQueryData(['todos']);
queryClient.setQueryData(['todos'], old => [...old, newTodo]);
return { prev };
}
onError: (_err, _vars, ctx) => queryClient.setQueryData(['todos'], ctx.prev)
- 避免重复请求:useQuery 的 enabled、select、keepPreviousData 等参数。
6、渲染与布局(CSS、动画、paint)
- 避免触发 layout thrash(强制回流)
- 避免在 JS 中频繁读取 layout 属性(offsetWidth);若必须,批量读取再批量写入。
- 使用 requestAnimationFrame 做读写交换。
- GPU 加速动画
- 动画使用 transform: translate3d、opacity;避免 top/left 大量布局变更。
- 使用 will-change 谨慎(只用于短期)。
- 减少重绘区域:把需要动画的元素单独放层,减少 paint 范围。
7、Hydration 与 RSC 相关实践
- Partial Hydration / Streaming
- 使用 streaming SSR,使关键 UI 先渲染,交互边界后 hydrating。
- 避免大规模 hydration
- 把很多 UI 做为静态 SSR(Server Component),只 hydrate 必要的 Client components。
- useId / SSR-friendly IDs 处理无冲突的 ID。
8、构建与交付优化(Bundle / Network)
- Bundle 分析:找到大包(lodash、date-fns、chart 库),替换为按需导入或轻量实现。
- Tree-shaking 与 按需导入。
- HTTP/2/3、brotli/gzip、资源缓存。
- Critical asset hints: LCP 图片 & 关键字体。
- Use modern bundler:Vite + Rollup(生产)、Turbopack(Next.js)提升 build & dev 体验。
四、架构级建议(中大型项目)
- 状态分层策略:UI state(local) vs UI-shared(Zustand) vs Server state(React Query) vs Persisted/global(RTK)。
- 组件目录原则:feature-based folders;每个 feature 自带 hooks、components、selectors。
- 性能守护(CI):在 CI 增加 bundle-size 检查、Lighthouse 基线测试、关键交互回归测试。
- 可观察性:埋点 LCP/INP、错误率、Action 成功率(尤其乐观更新失败率)。
- 迁移策略:逐步RSC迁移:先把静态、非交互视图迁到 Server Components,保留 client-only 才 hydrate。