—— 让你的应用更快、更流畅
💡 “过早优化是万恶之源”,但“关键路径上的性能优化”是专业开发者必备技能。
我们的目标不是盲目优化,而是 精准识别瓶颈 + 合理使用工具。
一、为什么需要性能优化?📉
虽然 React 的虚拟 DOM 已经很高效,但在以下场景仍可能出现卡顿:
| 场景 | 问题 |
|---|---|
| 列表渲染上千条数据 | 页面卡顿、滚动不流畅 |
| 频繁状态更新 | 不必要的重渲染(re-render) |
| 复杂计算每次重新执行 | 拖慢交互响应 |
| 组件过度嵌套 | 更新传播链太长 |
✅ 优化目标:
- 减少不必要的渲染
- 延迟加载非关键资源
- 缓存昂贵的计算结果
二、核心工具:React.memo / useMemo / useCallback 🛠️
这三个是 React 提供的性能优化原语,它们都基于“记忆化(Memoization)”思想 —— 避免重复工作。
1. React.memo:防止组件不必要渲染 ✅
默认行为:父组件更新 → 所有子组件默认也更新
function Parent() {
const [count, setCount] = useState(0);
return (
<div>
<h2>计数器: {count}</h2>
<button onClick={() => setCount(count + 1)}>+1</button>
{/* 这个组件其实不需要重新渲染 */}
<ExpensiveComponent data={veryLargeData} />
</div>
);
}
👉 即使 ExpensiveComponent 的 data 没变,它也会跟着父组件一起重渲染!
解决方案:用 React.memo 包裹
const ExpensiveComponent = React.memo(function ({ data }) {
// 复杂渲染逻辑
return <div>{/* 渲染大量内容 */}</div>;
});
✅ React.memo 会浅比较 props,如果没变化,就跳过渲染。
自定义比较函数(进阶)
const MemoizedComponent = React.memo(
({ a, b }) => {
/* render */
},
(prevProps, nextProps) => {
// 返回 true 表示“相同”,不重新渲染
return prevProps.a === nextProps.a && prevProps.b.id === nextProps.b.id;
}
);
2. useMemo:缓存昂贵的计算结果 💾
❌ 错误写法:每次渲染都重新计算
function MyComponent({ list, filterText }) {
// 每次渲染都会执行这个耗时操作!
const filtered = expensiveFilter(list, filterText);
return (
<ul>
{filtered.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
);
}
✅ 正确做法:使用 useMemo
function MyComponent({ list, filterText }) {
const filtered = useMemo(() => {
return expensiveFilter(list, filterText);
}, [list, filterText]); // 依赖项不变时,复用上次结果
return (
<ul>
{filtered.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
);
}
✅ 只有当 list 或 filterText 改变时才重新计算。
3. useCallback:缓存函数引用 🔗
问题:子组件频繁重渲染
function Parent() {
const [count, setCount] = useState(0);
// 每次渲染都会创建新函数 → 引用改变
const handleSave = () => {
console.log('保存数据');
};
return (
<div>
<Child onSave={handleSave} /> {/* 引用变了 → Child 被迫重渲染 */}
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}
✅ 使用 useCallback 缓存函数
const handleSave = useCallback(() => {
console.log('保存数据');
}, []); // 空依赖数组 → 函数在整个生命周期中保持不变
✅ 结合 React.memo 使用效果最佳:
const Child = React.memo(({ onSave }) => {
console.log('Child 渲染了一次'); // 仅在 onSave 变化时打印
return <button onClick={onSave}>保存</button>;
});
三、懒加载与代码分割 🧩
1. 路由级懒加载(前面已讲)
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
<Suspense fallback={<Spinner />}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</Suspense>
✅ Webpack 会自动拆分代码包,按需加载。
2. 组件级懒加载(动态导入)
适用于模态框、复杂图表等重型组件:
function Dashboard() {
const [showChart, setShowChart] = useState(false);
return (
<div>
<button onClick={() => setShowChart(true)}>查看图表</button>
{showChart && (
<Suspense fallback="加载中...">
<LazyChartComponent />
</Suspense>
)}
</div>
);
}
const LazyChartComponent = lazy(() => import('./Chart'));
四、使用 Profiler 分析性能 🔍
React 提供了 <Profiler> 来测量组件渲染成本。
function onRender(id, phase, actualDuration) {
console.log({
组件: id,
阶段: phase, // 'mount' | 'update'
渲染耗时: actualDuration, // 毫秒
});
}
// 在任意位置包裹
<Profiler id="Dashboard" onRender={onRender}>
<Dashboard />
</Profiler>
✅ 推荐在开发环境开启,找出耗时高的组件。
五、常见反模式与避坑指南 ⚠️
❌ 反模式 1:滥用 memo / useMemo
// ❌ 没必要!简单组件或小对象没必要缓存
const label = useMemo(() => <span>姓名</span>, []);
// ❌ 对象/函数在依赖项中每次都新建
useEffect(() => {
// ...
}, [{ name }]); // 每次都是新对象!
// ✅ 应该提取或使用 useRef
✅ 规则:只在真正昂贵的操作上使用 useMemo / useCallback
❌ 反模式 2:在循环中使用 memo
{items.map(item => (
<React.memo key={item.id} component={Row} /> // ❌ 无效!
))}
✅ 正确做法:
const Row = React.memo(({ item }) => <div>{item.name}</div>);
{items.map(item => (
<Row key={item.id} item={item} /> // ✅ 正确使用
))}
六、实战练习 🏋️♂️
✅ 练习 1:优化一个低效列表
有一个用户列表,每行有一个“编辑”按钮,点击弹窗。
- 问题:点击“编辑”导致整个列表重渲染。
- 目标:使用
React.memo + useCallback修复。
function UserList({ users }) {
const [editingId, setEditingId] = useState(null);
const handleEdit = useCallback((id) => {
setEditingId(id);
}, []);
return (
<div>
{users.map(user => (
<UserRow
key={user.id}
user={user}
onEdit={handleEdit}
isEditing={editingId === user.id}
/>
))}
</div>
);
}
const UserRow = React.memo(({ user, onEdit, isEditing }) => {
console.log('渲染:', user.name);
return (
<div>
{user.name} - {user.email}
<button onClick={() => onEdit(user.id)}>
{isEditing ? '正在编辑...' : '编辑'}
</button>
</div>
);
});
✅ 测试:点击编辑后,其他行不再重复打印日志。
✅ 练习 2:缓存过滤后的商品列表
function ProductList({ products, category, searchQuery }) {
const filtered = useMemo(() => {
return products
.filter(p => p.category === category)
.filter(p => p.name.includes(searchQuery));
}, [products, category, searchQuery]);
return (
<ul>
{filtered.map(p => <li key={p.id}>{p.name}</li>)}
</ul>
);
}
✅ 加入 console.log('执行过滤') 验证是否只在依赖变化时运行。
✅ 总结:性能优化核心原则
| 技术 | 适用场景 | 是否常用 |
|---|---|---|
React.memo | 子组件 props 不常变 | ✅ 高频 |
useCallback | 传递给子组件的回调函数 | ✅ 高频 |
useMemo | 昂贵计算、大型列表过滤 | ✅ 中频 |
lazy + Suspense | 路由/重型组件懒加载 | ✅ 必备 |
<Profiler> | 性能分析定位瓶颈 | ✅ 开发期使用 |
🎯 记住一句话:
- 先测量,再优化
- 不要过度优化简单组件
- 优先优化用户可感知的部分(如滚动、输入响应)
🎯 下一步预告:
你已经能让应用又快又好!接下来我们要进入现代 React 的巅峰:
➡️ 自定义 Hook + 最佳实践架构
—— 封装通用能力,打造可维护、可扩展的项目结构
是否继续?我将带你写出“大厂级别”的 React 架构设计。