React 是构建现代 Web 应用的重要框架,但由于其响应式渲染机制,稍不注意就可能引发重复渲染、性能浪费、UI 卡顿等问题。
本文从组件设计、渲染控制、状态管理、Hooks 使用、异步优化、Suspense 并发特性、工具监控七大板块,大概讲解一下每一种优化方式。
一、组件层面的性能优化
1. 使用 React.memo 避免不必要的子组件更新
const MemoUserCard = React.memo(({ name }) => {
console.log('MemoUserCard rendered');
return <div>{name}</div>;
});
React.memo 是一个高阶组件,用于包裹函数组件,只有在 props 变更时才重新渲染。适合 UI 纯粹依赖 props 的子组件。
2. 拆分组件粒度,控制最小更新单元
将一个大型组件按功能拆成多个子组件,并使用 memo 或自定义渲染控制,能有效避免因为无关状态变化导致整体重渲。
const Dashboard = ({ user, chartData }) => (
<>
<MemoUserCard user={user} />
<MemoChart data={chartData} />
</>
);
二、渲染控制策略
1. 使用 key 优化列表渲染
list.map((item) => <li key={item.id}>{item.name}</li>);
key 是 React Diff 算法的核心,稳定的 key 能显著减少重建 DOM 的次数。
2. 使用 shouldComponentUpdate 或 PureComponent(已过时)
过去会使用 shouldComponentUpdate 或 PureComponent 来控制类组件的重渲
class Profile extends React.PureComponent {
render() {
return <div>{this.props.name}</div>;
}
}
在 Hooks 普及后,这些方式已逐渐退出主流开发实践
三、Hooks 使用注意事项
1. 使用 useMemo 缓存计算结果
const sortedList = useMemo(() => {
return [...list].sort((a, b) => a.value - b.value);
}, [list]);
useMemo 可以避免每次渲染都重复计算。适合大数组排序、过滤等耗时逻辑。
2. 使用 useCallback 缓存函数引用
const handleClick = useCallback(() => console.log('Clicked'), []);
useCallback 可避免函数地址变化导致的子组件重新渲染,尤其是与 React.memo 结合使用时,能有效提升性能。
四、状态管理优化
1. 将状态放在真正需要它的组件中
避免将状态提升到不必要的父组件,从而引起子组件全部重渲。
// ❌ 所有子组件因 count 状态变化都被影响
const App = () => {
const [count, setCount] = useState(0);
return <Layout count={count} setCount={setCount} />;
};
// ✅ 仅 Counter 使用自身状态
const Counter = () => {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
};
2. 使用状态管理库(如 Zustand / Recoil)解耦依赖关系
Zustand 使用 selector(状态选择器)订阅状态,只有实际变化才更新组件,适合中大型项目性能控制。
// store.ts
import { create } from 'zustand';
const useStore = create(set => ({
count: 0,
increment: () => set(state => ({ count: state.count + 1 }))
}));
// Counter.tsx
const CountBtn = () => {
const count = useStore(state => state.count); // ✅ 精准订阅
const inc = useStore(state => state.increment);
return <button onClick={inc}>Count: {count}</button>;
};
这边可以提一嘴Zustand 这个东西和我们平常用的useReducer有什么区别
useReducer 示例(适合组件内的复杂状态)
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
default:
return state;
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<button onClick={() => dispatch({ type: 'increment' })}>
Count: {state.count}
</button>
);
}
Zustand 示例(适合多个组件共享)
const useStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}));
function Counter() {
const count = useStore((state) => state.count);
const increment = useStore((state) => state.increment);
return <button onClick={increment}>Count: {count}</button>;
}
function Total() {
const count = useStore((state) => state.count); // 跨组件使用同一状态
return <div>Total: {count}</div>;
}
| 对比项 | useReducer | Zustand |
|---|---|---|
| 用法 | React 原生 Hook | 第三方库(基于 Hook 封装) |
| 状态作用域 | 💡 组件内部状态 | 💡 跨组件、全局共享状态 |
| 更新方式 | 通过 dispatch(action) 更新 | 调用封装的 set 方法 |
| 状态结构 | 单一 reducer 管理统一 state 对象 | 可自由定义多个字段 + 方法(像 useState) |
| 渲染性能 | 每次更新都触发组件重渲染(全部) | 精准订阅某字段,局部更新(性能更好) |
| 写法难度 | 类似 Redux,需要定义 reducer、action | 写法简洁,像 useState 一样 |
| Provider 需求 | 不需要 | 也不需要(Zustand 自动全局管理) |
| 是否适合全局状态 | ❌ 组件级状态 | ✅ 全局状态场景理想选择 |
总结:
useReducer 是原生的、组件内状态管理方式,适合组件内部复杂状态变更;
Zustand 是更适合“全局状态管理”的工具,不需要 Context,也不需要 Redux,写法清晰、性能优秀。
五、异步请求与并发优化
1. 使用 AbortController 或请求标记避免竞态更新
useEffect(() => {
const controller = new AbortController();
const signal = controller.signal;
fetch(`/api?q=${query}`, { signal })
.then(res => res.json())
.then(setData)
.catch(err => {
if (err.name !== 'AbortError') console.error(err);
});
return () => controller.abort(); // ✅ 中断旧请求
}, [query]);
当用户频繁变更搜索条件等请求参数时,老请求若未中止,可能覆盖新结果,造成竞态问题。(平常那种商城端项目用的比较多)
六、React Suspense 与并发特性(React 18)
1. 使用 React.lazy + Suspense 实现组件懒加载
const LazyChart = React.lazy(() => import('./Chart'));
<Suspense fallback={<div>加载中...</div>}>
<LazyChart />
</Suspense>
减少首屏加载体积,按需加载非关键子组件资源。
2. 并发数据加载(还不稳定)
结合如 React Query、Relay 等库,可支持基于 Suspense 的数据并发加载:
<Suspense fallback={<Loading />}>
<PostDetail id={postId} />
</Suspense>
七、工具与调试
1. 使用 React DevTools + Profiler 分析组件性能
- 开启 Profiler 插件
- 识别每次渲染组件、耗时
- 分析导致重新渲染的 state/props 来源
2. 使用 why-did-you-render 检查不必要渲染
import React from 'react';
if (process.env.NODE_ENV === 'development') {
const whyDidYouRender = require('@welldone-software/why-did-you-render');
whyDidYouRender(React);
}
MemoUserCard.whyDidYouRender = true; // 行代码的作用是:告诉 why-did-you-render:“请监听这个组件的渲染”。
总结
| 优化点 | 技术方法 |
|---|---|
| 子组件重复渲染 | React.memo / useCallback |
| 大计算逻辑 | useMemo |
| 多函数回调 | useCallback |
| 列表 diff | 稳定 key |
| 状态影响范围大 | 状态局部化 / 状态库(Zustand/Recoil) |
| 异步竞态 | AbortController |
| 首屏资源重 | React.lazy / Suspense |
| 性能分析 | Profiler / why-did-you-render |
1139

被折叠的 条评论
为什么被折叠?



