React应用在复杂场景下容易出现渲染性能瓶颈,合理优化能显著提升用户体验。React性能优化手段的核心在于减少不必要的渲染、控制资源加载和合理使用缓存机制。
1. 使用 React.memo 避免子组件无意义重渲染
当父组件更新时,即使子组件props未变,也会默认重新渲染。React.memo可缓存组件输出,仅在props变化时重新更新。
示例Demo:
javascript
import React, { useState } from "react";
const ExpensiveComponent = React.memo(({ count }) => {
console.log("ExpensiveComponent 渲染了");
return <div>计算结果: {count * 100}</div>;
});
function App() {
const [count, setCount] = useState(0);
const [toggle, setToggle] = useState(false);
return (
<div style={{ padding: "20px", fontFamily: "sans-serif" }}>
<h2>React.memo 避免子组件重渲染</h2>
<button onClick={() => setCount((c) => c + 1)}>增加计数</button>
<button onClick={() => setToggle((t) => !t)}>切换状态</button>
<p>当前计数: {count}</p>
<ExpensiveComponent count={count} />
<p style={{ marginTop: "16px" }}>
切换状态不会触发 ExpensiveComponent 重渲染
</p>
</div>
);
}
export default App;

附上codesandbox链接:react-memo
关键点:React.memo默认做浅比较,适用于props为基本类型或不变对象的场景。
2. 使用 useMemo 缓存昂贵计算
对复杂计算(如过滤、排序、数学运算)使用useMemo避免每次渲染都重新执行。
示例Demo:
javascript
import React, { useState, useMemo } from "react";
function ExpensivePrimeCounter() {
const [limit, setLimit] = useState(0);
const [toggle, setToggle] = useState(false);
// 模拟昂贵计算:计算小于 limit 的质数个数
const primeCount = useMemo(() => {
console.log("✅ 重新计算质数个数(未被缓存)");
let count = 0;
for (let i = 2; i <= limit; i++) {
let isPrime = true;
for (let j = 2; j * j <= i; j++) {
if (i % j === 0) {
isPrime = false;
break;
}
}
if (isPrime) count++;
}
return count;
}, [limit]); // 仅当 limit 变化时重新计算
return (
<div
style={{
padding: "2rem",
fontFamily: "sans-serif",
maxWidth: "500px",
margin: "0 auto",
}}
>
<h2>质数计数器</h2>
<p>
当前上限:<strong>{limit}</strong>
</p>
<p>
小于 {limit} 的质数个数:<strong>{primeCount}</strong>
</p>
<button
onClick={() => setLimit((c) => c + 1)}
style={{ margin: "0.5rem" }}
>
增加上限
</button>
<button onClick={() => setToggle((t) => !t)}>切换状态</button>
<p style={{ fontSize: "0.8rem", color: "#666" }}>
注意:仅当上限变化时,质数计算才会重新执行 ------ 缓存生效!
</p>
</div>
);
}
export default ExpensivePrimeCounter;

附上codesandbox链接:react-useMemo
注意:不要滥用useMemo,仅对计算成本高的场景使用(如1000+条数据处理)
3. 使用 useCallback 避免回调函数重新创建
回调函数作为props传递给子组件时,每次渲染都会生成新函数,导致React.memo失效。
示例Demo:
javascript
import React from "react";
const Button = React.memo(({ onClick, label }) => {
console.log("Button rendered");
return <button onClick={onClick}>{label}</button>;
});
export default function Parent() {
const [count, setCount] = React.useState(0);
// ❌ 每次渲染都创建新函数 → Button 会重新渲染
// const handleClick = () => setCount(c => c + 1);
// ✅ 使用 useCallback 缓存函数引用
const handleClick = React.useCallback(() => {
setCount((c) => c + 1);
}, []);
return (
<div>
<p>Count: {count}</p>
<Button label="Increment" onClick={handleClick} />
</div>
);
}
附上codesandbox链接:react-useCallback
useCallback与React.memo配合使用,是优化组件树的关键组合。
4. 虚拟滚动:渲染大量列表时只渲染可见项
对长列表(如1000+项)使用虚拟滚动,避免DOM节点爆炸。
javascript
import React from 'react';
import { FixedSizeList as List } from 'react-window';
const Row = ({ index, style }) => (
<div style={style}>
Item {index}
</div>
);
function LongList({ items }) {
return (
<List
height={400}
itemCount={items.length}
itemSize={35}
width="100%"
>
{Row}
</List>
);
}
需安装依赖:react-window或react-virtualized
性能提升:从渲染1000个DOM节点 → 仅渲染10~20个可见节点
5. 懒加载组件:按需加载非关键模块
使用React.lazy + Suspense实现代码分割,减少首屏加载体积。
javascript
import React, { lazy, Suspense } from 'react';
const HeavyComponent = lazy(() => import('./HeavyComponent'));
function App() {
return (
<div>
<h1>Dashboard</h1>
<Suspense fallback={<div>Loading...</div>}>
<HeavyComponent />
</Suspense>
</div>
);
}
适用于:弹窗、配置页、图标组件等非首屏内容
可结合webpack实现自动代码分割,SSR模式下可以使用@loadable/component
6. 使用React DevTools分析渲染性能
安装 React DevTools 浏览器插件,启用"Highlight updates when components render"功能,直观看到哪些组件被不必要地重渲染。
总结
| 优化手段 | 适用场景 | 是否推荐 |
|---|---|---|
| React.memo | 子组件props稳定 | 强烈推荐 |
| useMemo | 复杂计算、昂贵函数 | 推荐 |
| useCallback | 回调传给memoized子组件 | 推荐 |
| 虚拟滚动 | 长列表(>500项) | 必须使用 |
| React.lazy/@loadable/component | 非首屏组件 | 推荐 |
不要过早优化。先保证功能正确,再用工具定位瓶颈,针对性优化。