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 ...
随机推荐
- zk基础—3.集群与核心参数
大纲 1.zk单机模式是如何启动的 2.zk集群是如何部署和启动的 3.zk集群部署要用什么样配置的机器 4.如何合理设置zk的JVM参数以及内存大小 5.zk配置的核心参数之tickTime.dat ...
- Lazarus信创之路:启程,自动升级程序
相信国内做Delphi开发的不在少数,信创大趋势下,很多转Lazarus开发了.最近我也研究了一下,决定也转到这下面来,主要考虑:1.商业化方便,无版权纠纷:2.兼容Delphi语法,上手很快:3.原 ...
- .NET 原生驾驭 AI 新基建实战系列(二):Semantic Kernel 整合对向量数据库的统一支持
1. 引言 在人工智能(AI)应用开发迅猛发展的今天,向量数据库作为存储和检索高维数据的重要工具,已经成为许多场景(如自然语言处理.推荐系统和语义搜索)的核心组件. 对于.NET生态系统的开发者而言, ...
- Window7搭建Kafka环境总结
1.安装zooeleeper 下载链接:http://mirror.bit.edu.cn/apache/zookeeper/zookeeper-3.4.14/ 安装步骤如下: 1)解压zookeepe ...
- cocos3.x creator常见问题及解决办法
原文地址: cocos3.x creator剪切.动画.物理引擎.碰撞检测等常见问题及解决办法 - 搜栈网 (seekstack.cn)https://www.seekstack.cn/post/4 ...
- 【经验】Office|重装后,PPT 2016后失去平滑等功能(解决方式:使用Office Tools Plus重新安装另一版本)
重装ppt 2016后没有平滑等功能,并且在淡入/淡出中的平滑播放的还是淡入/淡出.本文记录了解决方案. 如何下载带平滑功能的Office? 首先,不能从官网微软服务和订阅下载.因为官网的必须购买才能 ...
- AI 在软件测试中的应用:2025 年趋势、工具及入门指南
引言 人工智能 (AI) 正在深刻地重塑软件开发和质量保证 (QA) 的各个方面.尤其是在软件测试领域,AI 不再仅仅是未来愿景,而是当下正在发生的变革.据世界质量报告(2023-24)指出,高达 7 ...
- Linux系列:聊一聊 SystemV 下的进程间共享内存
一:背景 1. 讲故事 昨天在分析一个 linux 的 dump 时,看到了这么一话警告,参考如下: 0:000> !eeheap -gc *** WARNING: Unable to veri ...
- TypeScript实用技巧大杂烩,助你成为真正的全栈工程师
@charset "UTF-8"; .markdown-body { line-height: 1.75; font-weight: 400; font-size: 15px; o ...
- 深度解析JS事件驱动模型:如何理解浏览器中的异步回调和事件循环
@charset "UTF-8"; .markdown-body { line-height: 1.75; font-weight: 400; font-size: 15px; o ...