从 Vue 到 React:React.memo + useCallback 组合技
目录
- 一、Vue 与 React 的组件更新机制对比
- 二、React.memo 是什么?
- 三、常见坑:为什么我用了 React.memo 还是会重新渲染?
- 四、解决方案:useMemo / useCallback 缓存引用
- 五、Vue 3 中有类似的性能控制需求吗?
- 六、组合优化小技巧总结
- 七、不过话又说回来
一、Vue 与 React 的组件更新机制对比
在 Vue 中,组件的更新依赖于响应式系统的依赖追踪:
<!-- Vue 模板中 -->
<Child :data="data" />
- 父组件更新时,Vue 会判断
data
是否变更; - 若
data
是响应式对象,它会做“依赖追踪”; - 子组件是否更新由内部响应式系统决定,开发者很少手动优化。
而在 React 中:
<Child data={data} />
- React 默认只做浅比较;
- 只要
data
是一个新对象(哪怕值完全相同),就会触发Child
重新渲染; - 所以你需要用
React.memo
+useCallback
+useMemo
来手动优化性能。
二、React.memo 是什么?
const MemoizedComponent = React.memo(Component);
React.memo
是一个高阶组件,用于缓存函数组件的渲染结果,仅在 props 变化时才重新渲染。
它做了啥?
- 对 props 进行浅比较;
- 如果 props 没变(===),则跳过子组件渲染;
三、常见坑:为什么我用了 React.memo 还是会重新渲染?
来看一个经典例子:
const Parent = () => {const [count, setCount] = useState(0);const data = { text: "hello" }; // ⚠️ 每次渲染都是新对象!return (<><button onClick={() => setCount(c => c + 1)}>+</button><Child data={data} /></>);
};const Child = React.memo(({ data }) => {console.log("Child render");return <div>{data.text}</div>;
});
🔎 结果:
- 每点一次按钮,
Child
都会重新渲染! - 虽然
data
的值没有变,但对象引用变了({}
是新对象); - 所以
React.memo
判定 props 变了,触发更新。
四、解决方案:useMemo / useCallback 缓存引用
const data = useMemo(() => ({ text: "hello" }), []);
或者对于函数:
const handleClick = useCallback(() => {console.log("clicked");
}, []);
这就保证了 引用不变,从而让 React.memo
的比较机制生效。
完整示例:
const Parent = () => {const [count, setCount] = useState(0);const data = useMemo(() => ({ text: "hello" }), []);const handleClick = useCallback(() => {console.log("clicked");}, []);return (<><button onClick={() => setCount(c => c + 1)}>+</button><Child data={data} onClick={handleClick} /></>);
};const Child = React.memo(({ data, onClick }) => {console.log("Child render");return <button onClick={onClick}>{data.text}</button>;
});
🧠 现在:
- 点击按钮不会触发
Child
重新渲染; - 因为
data
和onClick
的引用没变; React.memo
正常工作,组件性能得到提升。
五、Vue 3 中有类似的性能控制需求吗?
Vue 3 的响应式机制天然做了很多“追踪 + 缓存”,组件不会因为 props 引用变化而轻易重新渲染,只要你不写复杂嵌套 watch / watchEffect,基本不用显式控制更新。
但 React 是“纯函数组件 + 浅比较 + 显式控制”模式,性能优化基本靠开发者手动干预。
六、组合优化小技巧总结
目标 | 工具组合 |
---|---|
避免组件重复渲染 | React.memo |
保证函数 prop 引用稳定 | useCallback |
保证对象 prop 引用稳定 | useMemo |
高性能组件拆分 + 精准更新控制 | React.memo + useCallback + useMemo |
七、不过话又说回来
关于这个优化的组合手段,在实际开发中,往往不是“用不用”,而是“什么时候用、用在哪、用多少”。盲目无脑使用很容易陷入“性能优化反而拖慢开发效率”的误区。
❌ 常见错误:
const value = useMemo(() => expensiveCalculation(data), [data]);
很多人会这么写,但:
- 如果
expensiveCalculation()
实际上并不耗时; - 或者
data
频繁变化,memo 无意义; - 那么你加了
useMemo
不但没有优化,还增加了复杂度。
所以重点是:只有在“真的影响性能”时才用。
那【何时该用】呢?
场景 | 是否建议使用 Memo 类 Hook |
---|---|
父组件频繁更新,子组件 props 不变 | ✅ 使用 React.memo + useCallback |
传递对象或函数给子组件 | ✅ 保持引用稳定,避免不必要更新 |
有昂贵计算(排序、大数据处理等) | ✅ 使用 useMemo 缓存计算结果 |
props 很简单,子组件渲染开销很小(比如一个 <span> ) | ❌ 不需要,优化反而复杂 |
自己写了一堆 memo,但还是更新很慢 | ❌ 可能是 Context/状态设计问题 |
拥有大量动态子组件(如大表格、虚拟滚动列表) | ✅ 搭配 memo 、useMemo 、分块渲染等 |
使用 Context 时 | ⚠️ memo 可能无效,需额外处理 |
下面看看两类重点问题
- Context 泄漏导致 memo 失效
<MyContext.Provider value={contextValue}><MemoizedChild />
</MyContext.Provider>
每次 context value 变,整个 Provider 下所有组件都会重新渲染,哪怕用了
React.memo
解决方式:
- 避免 context value 是新对象(
useMemo
包装); - 或者将 context 拆分成多个 Provider;
- 或使用第三方库如
zustand
做 context 分片;
- 大量嵌套组件的 props 传递链
假如你有一个页面,父组件的数据变化会层层传到第 6 层子组件,那你会发现:
- 就算第 6 层用
React.memo
,如果 props 是对象或函数,还是会触发更新; - 用
useMemo
/useCallback
会让代码变得繁琐难读。
解决方式:
- 使用全局状态管理(如
zustand
)代替 props; - 通过组合组件逻辑 & 提前拆分优化更新粒度;
- 或者用
memo
+useContextSelector
(experimental)做局部响应式。
Vue 的响应式机制帮我们自动完成“依赖追踪 + 缓存更新”,你不用担心引用变化;
但在 React 中,这些都需要你自己判断并手动处理。
所以:
- 不要滥用 memo 系列 Hook,性能优化是“按需用药”;
- 越复杂的页面越要拆组件,避免“父更新拖着全家跑”;
- 适度引入状态管理工具,别让
props
变成“传话筒”。