React Hooks中memo、useMemo、useCallBack的作用
一句话概括
memo、useMemo、useCallBack主要用于避免 React 组件的重复渲染,作为 性能优化 的一种手段,你可以根据场景合理的使用它们。
React组件的更新机制
在使用memo、useCallBack、useMemo前,我们需要先了解React组件的更新机制:React组件在默认情况下,父组件或兄弟组件触发更新后,会按照父组件、子组件的顺序重新渲染,并且即使子组件本身没有发生任何变化,也会重复触发更新。
举一个简单的例子, 目前我们有Parent、Child1、Child2 三个组件。
// parent.jsx
import Child1 from './child1';
import Child2 from './child2';
import { useState } from 'react';
const Parent = () => {
const [count, setCount] = useState(0);
console.log('Parent 组件更新');
return (
<div>
<div>计数: {count} <button onClick={() => setCount(count + 1)}>自增</button></div>
<Child1 />
<Child2/>
</div>
);
};
export default Parent;
// child1.jsx
const Child1 = () => {
console.log('Child1 组件更新');
return (
<div>
我是Child1
</div>
);
};
export default Child1;
// child2.jsx
const Child2 = () => {
console.log('Child2 组件更新');
return (
<div>
我是Child2
</div>
);
};
export default Child2;

我们观察控制台,发现三个组件的 function 全部被重新执行了,即使在这次更新中 Child1、Child2 组件的内容完全没有发生变化!
如果在实际项目中, Child1、Child2 组件包含高开销的计算,Parent 组件的更新会导致它们不断地重复渲染,这样会对性能产生比较大的影响。
那么有没有什么办法可以避免这种情况下的重复渲染,从而达到性能优化的目的?这个就是我们使用memo、useCallBack、useMemo的原因。
memo
如果你的组件不存在 props 或者 props 相同的情况下,使用 React.memo包裹(高阶组件的形式),可以避免组件多余无意义的更新动作。
React.memo 通过记忆组件渲染结果的方式来提高组件的性能表现,既React 将跳过渲染组件的操作并直接复用最近一次渲染的结果。
// parent.jsx
import Child1 from './child1';
import Child2 from './child2';
import { useState } from 'react';
const Parent = () => {
const [count, setCount] = useState(0);
console.log('Parent 组件更新');
const params = {msg:'哈哈'};
return (
<div>
<div>计数: {count} <button onClick={() => setCount(count + 1)}>自增</button></div>
<Child1 />
<Child2 params={params}/>
</div>
);
};
export default Parent;
// child1.jsx
import { memo } from "react";
const Child1 = memo(() => {
console.log('Child1 组件更新');
return (
<div>
我是Child1
</div>
);
});
export default Child1;
// child2.jsx
import { memo } from "react";
const Child2 = memo((props: any) => {
console.log('Child2 组件更新');
return (
<div>
我是Child2
</div>
);
});
export default Child2;

由于我们点击了按钮,触发了state的更新,Parent 组件的 function 被重新执行。但是因为 Child1 组件因为包裹了 memo,所以此次更新 并未牵连 子组件 Child1 一同更新 既执行 它的 function。那为什么同样包裹的 Child2 会重新执行呢?
这是因为 memo 在判断props是否变化时,是进行的浅比较,比如 空值 或 基本类型 的 props,但是若传递的属性是是引用类型的属性, 则在父组件在更新的时候 属性 params 都会被重新定义一遍,进而导致包裹 Child2 的 memo 内部认为 props 发生了变化,最终重新渲染了 Child2 组件。
useMemo正用于解决这样的问题!
useMemo
useMemo接受两个参数 创建函数和依赖项数组,它仅会在依赖项数组中的元素 发生改变时,才重新计算 memoized 值(通过 创建函数 return 出去)。这种优化有助于避免在每次渲染时都进行高开销的计算。
因此上面的代码我们完全可以这样改写:
// parent.jsx
import Child from './child';
import { useMemo, useState } from 'react';
const Parent = () => {
const [count, setCount] = useState(0);
console.log('Parent 组件更新');
const params = useMemo(()=>({msg:'哈哈'}),[])
return (
<div>
<div>计数: {count} <button onClick={() => setCount(count + 1)}>自增</button></div>
<Child params={params}/>
</div>
);
};
export default Parent;
// child.jsx
import { memo } from "react";
const Child = memo((props: any) => {
console.log('Child 组件更新');
return (
<div>
我是Child
</div>
);
});
export default Child;

可以看到 Child并无重新执行和渲染,这是由于Parent 中的 params 是一个 useMemo ,其方法内部本身没有依赖任何变量,因此它的依赖数组项为空。
这样做可以保证无论 Parent 组件是否更新,params 变量始终都会是同一个。进而也就不会出发 结合了memo的 Child 组件更新了。
至此,我们成功通过使用memo、useMemo的组合达到了我们最终的目标。
useCallBack
本质是 useMemo 的语法糖!用法唯一的区别是:useMemo返回的是传入的回调函数的执行结果,useCallBack返回的是传入的回调函数。
useCallBack 的使用场景是 当传递给子组件的属性是一个函数的时候, 返回该函数的引用。当依赖项变化时,返回新函数的引用;否则返回缓存的旧函数引用:简单来说就是 useMemo 适合 缓存 非函数的属性,而 useCallBack 适合 缓存 函数的属性。
// parent.jsx
import Child from './child';
import { useCallback, useState } from 'react';
const Parent = () => {
const [count, setCount] = useState(0);
console.log('Parent 组件更新');
const sayHello = useCallback(() => {
console.log('你好');
}, []);
return (
<div>
<div>
计数: {count} <button onClick={() => setCount(count + 1)}>自增</button>
</div>
<Child params={sayHello} />
</div>
);
};
export default Parent;
// child.jsx
import { memo } from "react";
const Child = memo((props: any) => {
console.log('Child 组件更新');
return (
<div>
我是Child, <button onClick={()=>props.params()}>Child按钮</button>
</div>
);
});
export default Child;

其它
对比 vue computed
从设计初衷来看,Vue 的 computed 和 React 的 useMemo 有着相似的本意,即通过缓存计算结果来优化性能,避免不必要的重复计算。它们都旨在解决以下问题:
- 避免重复计算:当某些值依赖于其他状态或数据时,如果每次渲染都重新计算,可能会浪费性能。
- 响应式更新:当依赖项变化时,自动重新计算并更新结果。
- 简化代码逻辑:将复杂的计算逻辑封装起来,使代码更清晰、更易维护。
| 目标 | Vue 的 computed |
React 的 useMemo |
|---|---|---|
| 缓存计算结果 | 通过缓存计算结果,避免重复计算。 | 通过缓存计算结果,避免重复计算。 |
| 响应式更新 | 当依赖的响应式数据变化时,自动重新计算。 | 当依赖项变化时,重新计算并返回新的值。 |
| 简化代码逻辑 | 将复杂的计算逻辑封装到 computed 中。 |
将复杂的计算逻辑封装到 useMemo 中。 |
与useEffect对比
useMemo 是同步执行,而 useEffect 是异步的。
import { useMemo, useState, useEffect } from 'react';
function TestComponent() {
const [count, setCount] = useState(0);
console.log('普通代码:在渲染期间同步执行');
useMemo(() => {
console.log('useMemo:在渲染期间同步执行');
}, [count]);
useEffect(() => {
console.log('useEffect:在渲染后异步执行,不阻塞渲染)');
}, [count]);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
export default TestComponent;

对渲染流程的影响
useMemo:- 同步执行,会阻塞渲染流程。如果
useMemo的回调函数中有复杂的计算,可能导致渲染延迟。 - 适用于轻量级计算或必须立即使用的值(如派生状态)。
- 如果计算结果需要参与当前渲染,必须使用
useMemo。
- 同步执行,会阻塞渲染流程。如果
useEffect:- 异步执行,不会阻塞渲染流程,适合处理副作用(如网络请求、DOM 操作)。
- 副作用操作不会影响当前渲染的结果,但可能会触发后续的重新渲染。
useMemo 的适用场景
派生状态计算
根据props或state计算出一个新的值,且需要立即在渲染中使用。const total = useMemo(() => items.reduce((sum, item) => sum + item.price, 0), [items]);
避免引用类型重新创建
缓存对象或数组,避免子组件因引用变化而重新渲染。const config = useMemo(() => ({ timeout: 1000 }), []); // 依赖为空,引用不变
性能敏感的计算
当计算成本较高时(如大数据过滤、排序)。const filteredList = useMemo(() => {
return largeList.filter(item => item.isActive);
}, [largeList]); // 仅当 largeList 变化时重新计算
useEffect 的适用场景
副作用操作
如网络请求、DOM 操作、订阅事件等。useEffect(() => {
fetchData().then(data => setData(data));
}, []);
响应状态变化
当某些状态变化后需要执行特定操作(如数据保存)。useEffect(() => {
saveToLocalStorage(user);
}, [user]); // user 变化时触发保存
清理操作
在组件卸载或依赖变化前执行清理(如取消订阅)。useEffect(() => {
const subscription = eventEmitter.subscribe(handleEvent);
return () => subscription.unsubscribe(); // 清理函数
}, []);
不要滥用
使用useMemo、useCallBack时,本身会产生额外的开销,并且这两个方法必须和memo搭配使用,否则很可能会变成负优化。
因此,在实际项目中,需要结合实际场景,评估重复渲染和创建useCallBack/useCallBack的开销来判断到底用不用useCallBack、useMemo。
React Hooks中memo、useMemo、useCallBack的作用的更多相关文章
- React Hooks中父组件中调用子组件方法
React Hooks中父组件中调用子组件方法 使用到的hooks-- useImperativeHandle,useRef /* child子组件 */ // https://reactjs.org ...
- react,vue中的key有什么作用?(key的内部原理)
1.虚拟DOM中的key的作用: key是虚拟dom对象的标识,当状态中的数据发生变化时,vue会根据新数据生成新的虚拟dom,随后vue进行新的虚拟dom与旧的虚拟dom的差异比较. 2.比较规则 ...
- React Hooks用法大全
前言 在 React 的世界中,有容器组件和 UI 组件之分,在 React Hooks 出现之前,UI 组件我们可以使用函数,无状态组件来展示 UI,而对于容器组件,函数组件就显得无能为力,我们依赖 ...
- React Hooks使用避坑指南
函数组件比类组件更加方便实现业务逻辑代码的分离和组件的复用,函数组件也比类组件轻量,没有react hooks之前,函数组件是无法实现LocalState的,这导致有localstate状态的组件无法 ...
- react之react Hooks
函数组件,没有 class 组件中的 componentDidMount.componentDidUpdate 等生命周期方法,也没有 State,但这些可以通过 React Hook 实现. Rea ...
- React Hooks 深入系列 —— 设计模式
本文是 React Hooks 深入系列的后续.此篇详细介绍了 Hooks 相对 class 的优势所在, 并介绍了相关 api 的设计思想, 同时对 Hooks 如何对齐 class 的生命周期钩子 ...
- composition api和react hooks的对比
一. 我的走位: 保持中立 1. 各有各的好处, 谁也别说谁 2. 一个东西带来的好处, 相应的副作用肯定也有, 人无完人 二 . vue3 的composition api 和 rea ...
- 关于React Hooks,你不得不知的事
React Hooks是React 16.8发布以来最吸引人的特性之一.在开始介绍React Hooks之前,让咱们先来理解一下什么是hooks.wikipedia是这样给hook下定义的: In c ...
- 谈谈react hooks的优缺点
前言Hook 是 React 16.8 的新增特性.它是完全可选的,并且100%向后兼容.它可以让你使用函数组件的方式,运用类组件以及 react 其他的一些特性,比如管理状态.生命周期钩子等.从概念 ...
- React Hooks & useCallback & useMemo
React Hooks & useCallback & useMemo https://reactjs.org/docs/hooks-reference.html#usecallbac ...
随机推荐
- Linux下安装node及npm
Linux下安装node 1.解压 $ tar zxf node-v8.9.0-linux-x64.tar.gz 2.移动到指定目录 $ mv node-v8.9.0-linux-x64 /usr/l ...
- 在MaxKB中实现准确的Chat TO SQL(BI)
主要面向考试成绩管理系统(目前支持旭日图.仪表盘柱状图.桑基图.漏斗图.河流图.数据聚合图.散点图.南丁格尔玫瑰图.饼状图.环形图.堆叠柱状图.堆叠折线图.堆叠面积图.面积图.折线图) 主要思路: 第 ...
- jmeter结果断言的几种方法
这篇文章里,我们已经知道了怎样实用json断言(https://www.cnblogs.com/becks/p/14951725.html) 接下来还有几种断言,一一介绍 一.响应断言,这种断言的逻辑 ...
- 开发者专用部署工具PasteSpider的V5正式版发布啦!(202504月版),更新说明一览
PasteSpider是一款以开发者角度设计的部署工具,支持把你的项目部署到Windows或者Linux服务器,支持5大模式Windows(IIS/Service),Linux(systemd),Do ...
- 『Plotly实战指南』--样式定制高级篇
在数据可视化领域,Plotly不仅是高效的绘图工具,更是设计师的创意画布. 当基础图表已无法满足品牌化需求时,样式定制能力将成为数据叙事的关键武器. 深入的样式定制能够帮助我们打造品牌化图表.实现精准 ...
- swagger加权限
参照: [Blog.Core开源]开发插件,给Swagger加权 - 腾讯云开发者社区-腾讯云 (tencent.com)
- 【记录】环境|Ubuntu18.04 中搭建 Python 开发和调试环境的完整记录
文章目录 安装Python并切换 1 安装某个版本 方式一:pyenv安装(强烈推荐) 方式二:apt安装(不推荐) Python3 Python2 查看所有apt装上的版本 2 切换python版本 ...
- SQL 日常练习 (十六)
最近接触了一波 RPA, 可以用来做一些数据采集的事情, 或者任意控制电脑上的软件, 感觉上是挺厉害的, 但我就是不想用, 尽管我尝试了一波, 最后还是放弃 了, 我还是喜欢纯代码的工作方式, 最为讨 ...
- 网络编程:UDP connect连接
UDP connect的作用 UDP connect函数的调用,并不会引起像TCP连接那样,和服务器目标端网络交互,并不会触发所谓的"握手"报文发送和应答. UDP套接字进行con ...
- Vmware workstation安装部署微软WSUS服务应用系统
简介 WSUS全称Windows Server Update Services,是微软开发的免费服务器角色,用于在企业内网中集中管理Windows系统及微软产品的更新分发.其前身为Windows ...