React.memo() 和 useMemo() 的用法与区别
React.memo() 和 useMemo() 都是 React 提供的性能优化 API,但它们解决的问题和使用场景完全不同。下面我将从多个维度详细解析它们的用法和区别。
一、React.memo() - 组件级记忆
1. 基本用法
const MyComponent = React.memo(function MyComponent(props) {
/* 使用 props 渲染 */
});
// 或箭头函数
const MyComponent = React.memo((props) => {
/* 使用 props 渲染 */
});
2. 核心功能
- 作用对象:包装整个函数组件
- 优化原理:对组件 props 进行浅比较,如果 props 没有变化,则跳过重新渲染
- 类比概念:类似于类组件的
PureComponent
3. 适用场景
- 纯展示型组件(输入相同的 props 总是输出相同结果)
- 渲染开销较大的组件
- 频繁重新渲染但 props 很少变化的组件
4. 自定义比较函数
const MyComponent = React.memo(
(props) => {/* 渲染 */},
(prevProps, nextProps) => {
// 返回 true 表示跳过重新渲染
return prevProps.id === nextProps.id;
}
);
5. 典型示例
const UserProfile = React.memo(({ user }) => {
console.log('UserProfile 渲染');
return <div>{user.name}</div>;
});
function App() {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(c => c + 1)}>点击 {count}</button>
<UserProfile user={{ name: '张三' }} />
</div>
);
}
// 点击按钮时 UserProfile 不会重新渲染
二、useMemo() - 值记忆
1. 基本用法
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
2. 核心功能
- 作用对象:包装计算量大的函数结果
- 优化原理:依赖项不变时返回缓存值,避免重复计算
- 类比概念:类似于计算属性(Vue)或记忆化函数
3. 适用场景
- 昂贵的计算(如复杂数学运算、大数据转换)
- 避免不必要的重新创建对象/数组
- 作为其他 Hook 的依赖项优化
4. 典型示例
function App({ list }) {
const [filter, setFilter] = useState('');
const filteredList = useMemo(() => {
console.log('重新计算列表');
return list.filter(item => item.includes(filter));
}, [list, filter]); // 只有 list 或 filter 变化时才重新计算
return (
<div>
<input value={filter} onChange={(e) => setFilter(e.target.value)} />
<ul>
{filteredList.map(item => <li key={item}>{item}</li>)}
</ul>
</div>
);
}
三、核心区别对比
维度 | React.memo() | useMemo() |
---|---|---|
作用对象 | 整个函数组件 | 单个值/计算结果 |
优化目标 | 避免不必要的组件重新渲染 | 避免重复计算昂贵操作 |
比较方式 | 浅比较 props(可自定义) | 依赖项数组比较(Object.is) |
使用位置 | 组件外部(包装组件) | 组件内部(在函数体中使用) |
返回值 | 记忆化的组件 | 记忆化的值 |
典型用例 | 纯展示组件、大型列表项 | 复杂计算、对象/数组创建 |
四、组合使用示例
const ExpensiveComponent = React.memo(({ data }) => {
const processedData = useMemo(() => {
return data.map(item => ({
...item,
fullName: `${item.firstName} ${item.lastName}`
}));
}, [data]);
return (
<ul>
{processedData.map(item => (
<li key={item.id}>{item.fullName}</li>
))}
</ul>
);
});
function App() {
const [count, setCount] = useState(0);
const [users, setUsers] = useState([]);
// 模拟数据获取
useEffect(() => {
fetchUsers().then(setUsers);
}, []);
return (
<div>
<button onClick={() => setCount(c => c + 1)}>计数: {count}</button>
<ExpensiveComponent data={users} />
</div>
);
}
五、使用注意事项
1. React.memo() 陷阱
- 不合理的 props 比较:深层对象比较可能适得其反
// 反模式 - 每次都会重新渲染 <MemoizedComponent style={{ color: 'red' }} /> // 正确做法 - 提取到常量 const redStyle = { color: 'red' }; <MemoizedComponent style={redStyle} />
- 子组件问题:如果子组件本身经常变化,记忆化可能无效
// 反模式 - onClick 每次都是新函数 <MemoizedComponent onClick={() => {}} /> // 正确做法 - 使用 useCallback const handleClick = useCallback(() => {}, []); <MemoizedComponent onClick={handleClick} />
2. useMemo() 陷阱
- 过早优化:简单计算不需要 useMemo
// 不需要 - 计算太简单 const total = useMemo(() => a + b, [a, b]); // 直接计算即可 const total = a + b;
- 依赖项遗漏:会导致返回过期值
// 错误 - 遗漏了 b 作为依赖项 const sum = useMemo(() => a + b, [a]);
六、性能优化决策树
七、总结与最佳实践
- 优先考虑设计:良好的组件拆分比滥用记忆化更有效
- 测量后优化:使用 React DevTools Profiler 确认性能瓶颈
- 合理使用记忆化:
React.memo
用于静态或低频变化的组件useMemo
用于昂贵计算或稳定引用useCallback
配合React.memo
使用(处理函数 props)
- 避免过度优化:记忆化本身有开销,简单场景可能适得其反
记住:性能优化应该是基于实际测量结果的有针对性的措施,而不是开发初期的默认实践。