前端工程Technical Deep Dive
React 性能优化实战 —— 避免重渲染的 15+ 种方案
发布时间2026/03/29
分类前端工程
预计阅读2 分钟
作者吴长龙
*
性能问题的根源
01.内容
性能问题的根源
React 性能问题的核心是不必要的重渲染。每次状态变化,React 会从该组件开始向下遍历整棵 Fiber 树,找出需要更新的部分。理解这一点,才能有的放矢地进行优化。
一、组件级优化
#### 1. React.memo - 缓存组件
jsx snippetjsx
// 不使用 memo:父组件任何状态变化都会导致 Child 重渲染
function Parent() {
const [count, setCount] = useState(0);
return <Child />;
}
// 使用 memo:只有 props 变化才重渲染
const Child = memo(function Child({ name }) {
return <div>{name}</div>;
});
// 自定义比较函数
const Child = memo(function Child({ name }) {
return <div>{name}</div>;
}, (prevProps, nextProps) => {
// 返回 true 表示相等,不重渲染
return prevProps.name === nextProps.name;
});注意:默认使用浅比较,如果 props 是对象,需要注意引用变化。
#### 2. useMemo - 缓存计算结果
jsx snippetjsx
function ExpensiveComponent({ list, filter }) {
// 不优化:每次渲染都重新计算
const filtered = list.filter(item => item.name.includes(filter));
// 优化:只在依赖变化时重新计算
const filtered = useMemo(
() => list.filter(item => item.name.includes(filter)),
[list, filter]
);
return <ul>{filtered.map(...)}</ul>;
}#### 3. useCallback - 稳定函数引用
jsx snippetjsx
function Parent() {
const [count, setCount] = useState(0);
// 问题:每次渲染都是新函数,导致子组件重渲染
const handleClick = () => console.log(count);
// 解决:稳定引用
const handleClick = useCallback(() => {
console.log(count);
}, [count]); // 或者 [] 如果不依赖 count
return <Child onClick={handleClick} />;
}二、状态优化
#### 4. 状态细化
jsx snippetjsx
// 问题:无关状态放在一起
function BadComponent() {
const [state, setState] = useState({
user: null,
loading: false
});
// loading 变化会导致 user 相关组件重渲染
return <UserProfile user={state.user} />;
}
// 解决:拆分成独立状态
function GoodComponent() {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(false);
return <UserProfile user={user} />;
}#### 5. 状态提升的层次
jsx snippetjsx
// 问题:状态放得太高
function GrandParent() {
const [count, setCount] = useState(0);
return <Parent count={count} />;
}
function Parent({ count }) {
return <Child count={count} />; // 每一层都要传
}
function Child({ count }) {
return <div>{count}</div>;
}
// 解决:状态放到需要的地方
function Parent() {
return <ChildWithOwnState />;
}
function ChildWithOwnState() {
const [count, setCount] = useState(0);
return <div onClick={() => setCount(c => c + 1)}>{count}</div>;
}三、Context 优化
#### 6. 拆分 Context
jsx snippetjsx
// 问题:一个 Context 变化,所有消费者都重渲染
const AppContext = createContext();
function App() {
return (
<AppContext.Provider value={{ user, theme, setTheme }}>
<Toolbar />
<Content />
</AppContext.Provider>
);
}
// 解决:拆分成多个 Context
const UserContext = createContext();
const ThemeContext = createContext();
function App() {
return (
<UserContext.Provider value={user}>
<ThemeContext.Provider value={{ theme, setTheme }}>
<Toolbar />
<Content />
</ThemeContext.Provider>
</UserContext.Provider>
);
}#### 7. 动态 Context 值
jsx snippetjsx
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
// 问题:每次 theme 变化都创建新对象
const value = { theme, setTheme };
// 解决:使用 useMemo 缓存
const value = useMemo(() => ({ theme, setTheme }), [theme]);
return (
<ThemeContext.Provider value={value}>
{children}
</ThemeContext.Provider>
);
}四、列表优化
#### 8. 虚拟列表
jsx snippetjsx
import { FixedSizeList } from 'react-window';
function VirtualList({ items }) {
const Row = ({ index, style }) => (
<div style={style}>{items[index].name}</div>
);
return (
<FixedSizeList
height={400}
itemCount={items.length}
itemSize={50}
width="100%"
>
{Row}
</FixedSizeList>
);
}#### 9. 列表 key 的艺术
jsx snippetjsx
// 问题:使用索引作为 key
function BadList({ items }) {
return items.map((item, index) => (
<Child key={index} item={item} />
));
}
// 解决:使用稳定 ID
function GoodList({ items }) {
return items.map(item => (
<Child key={item.id} item={item} />
));
}五、渲染控制
#### 10. 条件渲染优化
jsx snippetjsx
// 问题:不必要的渲染
function Component() {
const [show, setShow] = useState(false);
return (
<div>
{show && <HeavyComponent />}
<button onClick={() => setShow(!show)}>Toggle</button>
</div>
);
}
// 解决:使用懒加载
const HeavyComponent = lazy(() => import('./HeavyComponent'));
function Component() {
const [show, setShow] = useState(false);
return (
<div>
{show && (
<Suspense fallback={<Loading />}>
<HeavyComponent />
</Suspense>
)}
<button onClick={() => setShow(!show)}>Toggle</button>
</div>
);
}#### 11. useTransition - 优先处理交互
jsx snippetjsx
function SearchResults() {
const [query, setQuery] = useState('');
const [isPending, startTransition] = useTransition();
const handleChange = (e) => {
const value = e.target.value;
// 标记为低优先级,不阻塞输入
startTransition(() => {
setQuery(value);
});
};
return (
<div>
<input onChange={handleChange} />
{isPending && <Loading />}
<Results query={query} />
</div>
);
}六、Effect 优化
#### 12. 避免 Effect 中的昂贵操作
jsx snippetjsx
// 问题:Effect 中每次渲染都执行
useEffect(() => {
const analytics = getAnalytics();
analytics.track(event);
}, [event]);
// 解决:使用 useRef 限制频率
const tracked = useRef(new Set());
useEffect(() => {
if (!tracked.current.has(event)) {
tracked.current.add(event);
const analytics = getAnalytics();
analytics.track(event);
}
}, [event]);#### 13. 依赖项精确化
jsx snippetjsx
// 问题:依赖过多导致频繁执行
useEffect(() => {
doSomething(a, b, c, d);
}, [a, b, c, d]);
// 解决:只依赖需要的值,或者使用 useCallback
const doSomething = useCallback((val) => {
// 只使用 a
}, [a]);
useEffect(() => {
doSomething(b);
}, [doSomething, b]);七、开发者工具
#### 14. React DevTools Profiler
jsx snippetjsx
// 开启性能分析
import { Profiler } from 'react';
function onRender(id, phase, actualTime, baseTime, ...){
console.log(`${id} ${phase}: ${actualTime}ms`);
}
<Profiler id="MyComponent" onRender={onRender}>
<MyComponent />
</Profiler>#### 15. why-did-you-render
javascript snippetjavascript
// 安装:npm install @welldone-software/why-did-you-render
import whyDidYouRender from '@welldone-software/why-did-you-render';
whyDidYouRender(React, {
trackAll: true,
trackHooks: true,
logOwnerReasons: true,
});性能优化检查清单
| 检查项 | 优先级 |
|---|---|
| 使用 React.memo 包装纯展示组件 | 高 |
| 大列表使用虚拟滚动 | 高 |
| useMemo/useCallback 缓存昂贵计算 | 中 |
| 拆分 Context | 中 |
| 状态细化到最小范围 | 中 |
| 使用 useTransition 处理非紧急更新 | 低 |
总结
React 性能优化是一个系统工程:
- •测量优先:先用 Profiler 找到瓶颈
- •从大问题着手:解决 20% 的问题带来 80% 的提升
- •避免过度优化:不是每个组件都需要 memo
- •理解权衡:优化往往伴随复杂度增加
记住:可读、可维护的代码 > 过度优化的代码。