React.memo
它是什么、做什么的,概念理解
React.memo 是 React 提供的一个高阶组件(Higher-Order Component, HOC) ,用于对函数组件进行浅层记忆化(shallow memoization) ,从而避免在 props 没有变化时进行不必要的重新渲染,提升性能。
怎么用:
import React from 'react';
const MyComponent = React.memo((props) => {
return <div>{props.value}</div>;
});
MyComponent是一个函数组件。- 使用
React.memo包裹后,React 会在每次父组件重新渲染时,先比较当前 props 和上一次的 props。 - 如果 props 浅比较相等(shallowly equal) ,则跳过本次渲染,直接复用上次的渲染结果。
⚠️ 注意:
React.memo只对 props 进行比较,不处理 state、context 或 hooks 的变化。
浅比较(Shallow Comparison)规则
React.memo 默认使用 浅比较 来判断 props 是否变化:
- 对于 原始类型(string、number、boolean、null、undefined、symbol) :值相等即视为相同。
- 对于 对象、数组、函数:仅比较引用是否相同(即
===),即使内容完全一样,只要引用不同,就认为 props 发生了变化。
1. 示例:浅比较失效的情况
function Parent() {
const [count, setCount] = useState(0);
// 每次渲染都创建新对象 → 引用不同
const data = { value: 'hello' };
return (
<>
<button onClick={() => setCount(c => c + 1)}>+</button>
<Child data={data} /> {/* Child 会每次都重新渲染! */}
</>
);
}
const Child = React.memo(({ data }) => {
console.log('Child rendered');
return <div>{data.value}</div>;
});
虽然 data 内容没变,但每次都是新对象,引用不同 → React.memo 无效。
✅ 解决方法:
-
使用
useMemo缓存对象:const data = useMemo(() => ({ value: 'hello' }), []); -
或确保传递的 prop 引用稳定(如使用
useCallback处理函数)。
自定义比较函数(可选)
你可以传入第二个参数给 React.memo,提供自定义的比较逻辑:
const Child = React.memo(
({ a, b, onUpdate }) => {
return <div>{a} - {b}</div>;
},
(prevProps, nextProps) => {
// 返回 true:props 相等,不重新渲染
// 返回 false:props 不同,需要重新渲染
return prevProps.a === nextProps.a && prevProps.b === nextProps.b;
// 注意:通常不比较函数(如 onUpdate),除非你确定它稳定
}
);
📌 自定义比较函数的返回值含义与
shouldComponentUpdate相反:
true表示“不需要更新”false表示“需要更新”
使用场景:
✅ 推荐使用 React.memo 的情况:
- 组件是 纯展示型(presentational) ,只依赖 props。
- 组件 渲染开销较大(如包含复杂计算、大量 DOM 节点)。
- 父组件频繁更新,但该子组件的 props 实际很少变化。
- 配合
useCallback/useMemo确保传入的函数/对象引用稳定。
❌ 不推荐滥用:
- 组件很小、渲染成本低 → 加
React.memo反而增加比较开销。 - props 中包含经常变化的对象/函数,且未做缓存 →
React.memo无效。 - 组件依赖 Context 或内部有状态(state)→
React.memo无法阻止因 context/state 变化导致的重渲染。
🔍 注意:
React.memo不能阻止以下情况的重渲染:
- 组件自身调用
useState、useReducer触发更新。- 组件消费了
Context,而 Context 的值发生变化。- 父组件强制更新(如使用
key变更)。
注意事项:
React.memo是函数组件的性能优化工具,通过浅比较 props 避免重复渲染。- 它只对 props 有效,且依赖引用稳定性。
- 必须配合
useCallback(函数)和useMemo(对象/数组)才能发挥最大效果。 - 不要默认给所有组件加
React.memo,应基于性能分析(如 React DevTools Profiler)按需使用。 - 自定义比较函数可用于复杂场景,但要小心性能开销。
💡 最佳实践:先写出清晰的代码,在发现性能瓶颈后再优化,避免过早优化带来的复杂性。
useCallback
它主要是用来缓存函数本身的; 当组件内的state改变,如果函数依赖没有改变就不重新创建函数;
前置知识:
react 如何触发页面的渲染:
import { useState } from 'react';
const [count, setCount] = useState(0);
setState 时会出触发当前页面的重新更新;故当前页面内的所有组件也会 重新渲染;
问题来了,有一些组件可能并不需要重新渲染,可能它传递的props没有改变,但是组件还是会从新渲染;
如何来规避这些组件的无效渲染:
- useCallback 缓存函数
- useMemo 缓存函数返回结果(类似vue中的 computed)
- React.memo 用于对传入的props进行浅比较,true则不刷新页面,false就重新加载组件
什么是 useCallback
useCallback 是 React 提供的一个 Hook,用于优化性能,它能够缓存函数,避免在组件重新渲染时不必要的函数重新创建。
基本语法
const memoizedCallback = useCallback(
() => {
// 回调函数体
},
[dependencies] // 依赖数组
);
- 第一个参数:要缓存的函数。
- 第二个参数:依赖数组(与
useEffect类似),只有当依赖项发生变化时,才会返回一个新的函数;否则返回之前缓存的函数引用。
为什么需要 useCallback
在 React 中,当组件重新渲染时,其内部的所有函数都会被重新创建。对于传递给子组件的回调函数来说,这意味着:
- 每次父组件渲染都会创建一个新的函数实例
- 子组件会因为接收到的 props 不同而重新渲染,即使实际内容没有变化
- 在依赖数组中使用的函数如果不被缓存,可能导致 effect 无限执行
useCallback 通过缓存函数实例来解决这些问题。
useMemo
useMemo 主要用于缓存计算结果,避免在每次组件渲染时都重复执行开销较大的计算逻辑。
类似于vue中的computed
怎么用:
const memoizedValue = useMemo(() => {
// 执行昂贵的计算
return computeExpensiveValue(a, b);
}, [a, b]); // 依赖数组
- 第一个参数:一个函数,返回需要缓存的值。
- 第二个参数:依赖数组(deps),只有当数组中的值发生变化时,才会重新执行计算函数;否则返回之前缓存的结果。
✅ 核心作用:跳过不必要的计算,提升性能。
使用场景:
在函数组件中,每次渲染都会重新执行整个函数体。如果其中有复杂计算(如遍历大数组、深度递归、格式化大量数据等),就会造成性能浪费。
✅ 场景 1:缓存复杂计算结果
const sortedList = useMemo(() =>
list.sort((a, b) => a.name.localeCompare(b.name)),
[list]
);
✅ 场景 2:创建稳定对象/数组引用(配合 React.memo)
const config = useMemo(() => ({
theme: 'dark',
lang: 'zh'
}), []); // 确保引用不变,避免子组件不必要重渲染
✅ 场景 3:避免在渲染中创建新实例
// ❌ 每次渲染都新建 Date 对象(虽小但可能影响子组件)
const today = new Date();
// ✅ 如果不需要响应时间变化,可缓存
const today = useMemo(() => new Date(), []);
✅ 场景 4:结合 Context 避免 Provider 不必要更新
const value = useMemo(() => ({ user, updateUser }), [user]);
return <UserContext.Provider value={value}>...</UserContext.Provider>;
防止因 value 引用变化导致所有消费者重渲染。
注意事项与陷阱
⚠️ 1. 不要滥用 useMemo
- 对于简单计算(如
a + b),使用useMemo反而增加内存和比较开销。 - 先写清晰代码,再根据性能分析(Profiler)决定是否优化。
⚠️ 2. 依赖项必须完整且正确
// ❌ 错误:缺少依赖
const result = useMemo(() => expensiveFn(x), []); // x 变化时不会更新!
// ✅ 正确
const result = useMemo(() => expensiveFn(x), [x]);
否则会导致 stale closure(闭包过期) —— 使用的是旧值。
⚠️ 3. 不要用 useMemo 执行副作用
// ❌ 错误:useMemo 不是 useEffect!
useMemo(() => {
localStorage.setItem('data', JSON.stringify(data));
return data;
}, [data]);
→ 副作用应放在 useEffect 中。
⚠️ 4. 数组/对象依赖项需稳定
// ❌ 每次渲染都创建新数组 → 依赖永远“变化”
const items = useMemo(() => filter(items, condition), [items, [condition]]);
// ✅ 应确保 condition 是稳定值(如 state 或 useMemo 缓存)
总结
-
useMemo用于缓存计算结果,避免重复昂贵操作。 -
它通过依赖数组控制何时重新计算。
-
主要用于:
- 优化性能(大计算、大数据处理)
- 创建稳定对象/数组引用(配合
React.memo) - 减少 Context Provider 的不必要更新
-
不要为了优化而优化,优先保证代码可读性。
-
务必正确填写依赖项,避免 stale closure。
💡 经验法则:当你发现某个计算在组件每次渲染时都执行,且该计算较重或结果用于 props 传递时,考虑
useMemo。