React 性能优化
本章将介绍 React 应用中常见的性能问题及其优化方法,特别是如何利用 React 18+ 的新特性来提升用户体验。
性能优化核心思路
- 减少渲染次数:避免不必要的组件更新。
- 减小渲染压力:将耗时任务拆分或延迟。
- 缓存计算结果:避免重复执行昂贵的计算逻辑。
1. 避免不必要的重新渲染
React.memo
React.memo 是一个高阶组件,它会对组件的 props 进行浅比较。如果 props 没有变化,React 将跳过渲染该组件。
实时编辑器
const Child = React.memo(({ name }) => { console.log("子组件渲染了"); return <div style={{ padding: '10px', border: '1px solid blue' }}>你好, {name}</div>; }); function Parent() { const [count, setCount] = useState(0); const [name, setName] = useState("张三"); return ( <div> <p>父组件计数值: {count}</p> <button onClick={() => setCount(c => c + 1)}>增加计数(不会触发子组件重绘)</button> <button onClick={() => setName(n => n === "张三" ? "李四" : "张三")} style={{ marginLeft: '10px' }}> 修改名称(会触发子组件重绘) </button> <hr /> <Child name={name} /> </div> ); }
结果
Loading...
2. 缓存计算与回调
useMemo 和 useCallback
useMemo:缓存计算结果。useCallback:缓存函数定义。
实时编辑器
function ExpensiveCalculation() { const [count, setCount] = useState(0); const [text, setText] = useState(""); // 模拟昂贵的计算 const expensiveValue = useMemo(() => { console.log("执行昂贵计算..."); let result = 0; for (let i = 0; i < 1000000; i++) { result += i; } return result + count; }, [count]); // 仅在 count 变化时重新计算 return ( <div> <h3>结果: {expensiveValue}</h3> <button onClick={() => setCount(c => c + 1)}>更新计数 (触发重新计算)</button> <input value={text} onChange={(e) => setText(e.target.value)} placeholder="输入内容不触发昂贵计算" style={{ marginLeft: '10px' }} /> </div> ); }
结果
Loading...
3. 并发渲染 (React 18+)
React 18 引入了并发特性,允许我们将更新标记为不同的优先级。
useTransition
用于将紧急更新(如输入文字)与非紧急更新(如过滤列表)分离。
实时编辑器
function FilterList() { const [query, setQuery] = useState(""); const [list, setList] = useState([]); const [isPending, startTransition] = useTransition(); const handleChange = (e) => { const value = e.target.value; setQuery(value); // 紧急更新:立即反映输入 // 非紧急更新:在后台处理列表过滤 startTransition(() => { const items = Array.from({ length: 5000 }, (_, i) => `${value} 项目 ${i}`); setList(items); }); }; return ( <div> <input value={query} onChange={handleChange} placeholder="键入文字查看并发渲染..." /> {isPending ? <p>正在计算过滤结果...</p> : ( <ul style={{ maxHeight: '200px', overflow: 'auto' }}> {list.slice(0, 100).map((item, i) => <li key={i}>{item}</li>)} </ul> )} </div> ); }
结果
Loading...
4. 代码分割
lazy 和 Suspense
通过动态导入组件,减少首屏加载的包体积。
import { lazy, Suspense } from 'react';
const HeavyComponent = lazy(() => import('./HeavyComponent'));
function App() {
return (
<Suspense fallback={<div>加载中...</div>}>
<HeavyComponent />
</Suspense>
);
}
5. 虚拟列表
当渲染包含数千个条目的列表时,使用虚拟化技术(只渲染可见区域的 DOM)。推荐库:
react-window@tanstack/react-virtual
小结
- React.memo:跳过未变化的组件渲染。
- useMemo/useCallback:缓存昂贵逻辑和函数引用。
- useTransition:优化长列表过滤或大数据更新的交互感。
- Suspense:实现优雅的异步加载和代码分割。
练习
- 使用
React.memo优化一个复杂的表单行组件。 - 给一个搜索框添加
useTransition,观察在大数据列表下的输入流畅度提升。 - 使用
useMemo优化一个基于多个过滤条件的商品列表。
常见性能问题及解决方案
| 问题 | 症状 | 解决方案 |
|---|---|---|
| 不必要的重新渲染 | 组件频繁渲染 | React.memo、useMemo、useCallback |
| 大列表渲染慢 | 滚动卡顿 | 虚拟列表、分页、懒加载 |
| 重复计算 | CPU 占用高 | useMemo |
| 重复创建函数 | 内存占用高 | useCallback |
| 大 Bundle | 首屏加载慢 | 代码分割、懒加载 |
| 内存泄漏 | 内存持续增长 | 清理 useEffect、取消订阅 |
性能检查清单
- ✅ 使用 React DevTools Profiler 分析
- ✅ 检查不必要的重新渲染
- ✅ 使用 React.memo 包装纯组件
- ✅ 使用 useMemo 缓存计算结果
- ✅ 使用 useCallback 缓存回调函数
- ✅ 使用虚拟列表处理大列表
- ✅ 实现代码分割和懒加载
- ✅ 生产环境构建并优化
- ✅ 清理副作用和取消订阅
- ✅ 监控性能指标
小结
- 避免不必要的重新渲染:React.memo、useMemo、useCallback
- 代码分割:React.lazy、Suspense
- 列表优化:虚拟列表、分页
- Context 优化:分离 Context、useMemo
- 防抖节流:减少高频事件触发
- 工具:React DevTools Profiler、webpack-bundle-analyzer
练习
- 创建一个列表组件,渲染 1000 条数据,并使用虚拟列表优化
- 实现一个搜索组件,使用防抖延迟搜索请求
- 创建一个表单组件,使用 useMemo 缓存验证逻辑
- 实现代码分割,只在需要时加载组件