一句话概括

memouseMemouseCallBack主要用于避免 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 的 computedReact 的 useMemo 有着相似的本意,即通过缓存计算结果来优化性能,避免不必要的重复计算。它们都旨在解决以下问题:

  1. 避免重复计算:当某些值依赖于其他状态或数据时,如果每次渲染都重新计算,可能会浪费性能。
  2. 响应式更新:当依赖项变化时,自动重新计算并更新结果。
  3. 简化代码逻辑:将复杂的计算逻辑封装起来,使代码更清晰、更易维护。

目标 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 的适用场景

  1. 派生状态计算

    根据 propsstate 计算出一个新的值,且需要立即在渲染中使用。

    const total = useMemo(() => items.reduce((sum, item) => sum + item.price, 0), [items]);
  2. 避免引用类型重新创建

    缓存对象或数组,避免子组件因引用变化而重新渲染。

    const config = useMemo(() => ({ timeout: 1000 }), []); // 依赖为空,引用不变
  3. 性能敏感的计算

    当计算成本较高时(如大数据过滤、排序)。

    const filteredList = useMemo(() => {
    return largeList.filter(item => item.isActive);
    }, [largeList]); // 仅当 largeList 变化时重新计算

useEffect 的适用场景

  1. 副作用操作

    如网络请求、DOM 操作、订阅事件等。

    useEffect(() => {
    fetchData().then(data => setData(data));
    }, []);
  2. 响应状态变化

    当某些状态变化后需要执行特定操作(如数据保存)。

    useEffect(() => {
    saveToLocalStorage(user);
    }, [user]); // user 变化时触发保存
  3. 清理操作

    在组件卸载或依赖变化前执行清理(如取消订阅)。

    useEffect(() => {
    const subscription = eventEmitter.subscribe(handleEvent);
    return () => subscription.unsubscribe(); // 清理函数
    }, []);

不要滥用

使用useMemo、useCallBack时,本身会产生额外的开销,并且这两个方法必须和memo搭配使用,否则很可能会变成负优化。

因此,在实际项目中,需要结合实际场景,评估重复渲染和创建useCallBack/useCallBack的开销来判断到底用不用useCallBack、useMemo。

React Hooks中memo、useMemo、useCallBack的作用的更多相关文章

  1. React Hooks中父组件中调用子组件方法

    React Hooks中父组件中调用子组件方法 使用到的hooks-- useImperativeHandle,useRef /* child子组件 */ // https://reactjs.org ...

  2. react,vue中的key有什么作用?(key的内部原理)

    1.虚拟DOM中的key的作用: key是虚拟dom对象的标识,当状态中的数据发生变化时,vue会根据新数据生成新的虚拟dom,随后vue进行新的虚拟dom与旧的虚拟dom的差异比较. 2.比较规则 ...

  3. React Hooks用法大全

    前言 在 React 的世界中,有容器组件和 UI 组件之分,在 React Hooks 出现之前,UI 组件我们可以使用函数,无状态组件来展示 UI,而对于容器组件,函数组件就显得无能为力,我们依赖 ...

  4. React Hooks使用避坑指南

    函数组件比类组件更加方便实现业务逻辑代码的分离和组件的复用,函数组件也比类组件轻量,没有react hooks之前,函数组件是无法实现LocalState的,这导致有localstate状态的组件无法 ...

  5. react之react Hooks

    函数组件,没有 class 组件中的 componentDidMount.componentDidUpdate 等生命周期方法,也没有 State,但这些可以通过 React Hook 实现. Rea ...

  6. React Hooks 深入系列 —— 设计模式

    本文是 React Hooks 深入系列的后续.此篇详细介绍了 Hooks 相对 class 的优势所在, 并介绍了相关 api 的设计思想, 同时对 Hooks 如何对齐 class 的生命周期钩子 ...

  7. composition api和react hooks的对比

    一.  我的走位:   保持中立 1. 各有各的好处,  谁也别说谁 2. 一个东西带来的好处, 相应的副作用肯定也有, 人无完人 二 .  vue3 的composition api 和   rea ...

  8. 关于React Hooks,你不得不知的事

    React Hooks是React 16.8发布以来最吸引人的特性之一.在开始介绍React Hooks之前,让咱们先来理解一下什么是hooks.wikipedia是这样给hook下定义的: In c ...

  9. 谈谈react hooks的优缺点

    前言Hook 是 React 16.8 的新增特性.它是完全可选的,并且100%向后兼容.它可以让你使用函数组件的方式,运用类组件以及 react 其他的一些特性,比如管理状态.生命周期钩子等.从概念 ...

  10. React Hooks & useCallback & useMemo

    React Hooks & useCallback & useMemo https://reactjs.org/docs/hooks-reference.html#usecallbac ...

随机推荐

  1. zk基础—3.集群与核心参数

    大纲 1.zk单机模式是如何启动的 2.zk集群是如何部署和启动的 3.zk集群部署要用什么样配置的机器 4.如何合理设置zk的JVM参数以及内存大小 5.zk配置的核心参数之tickTime.dat ...

  2. Lazarus信创之路:启程,自动升级程序

    相信国内做Delphi开发的不在少数,信创大趋势下,很多转Lazarus开发了.最近我也研究了一下,决定也转到这下面来,主要考虑:1.商业化方便,无版权纠纷:2.兼容Delphi语法,上手很快:3.原 ...

  3. .NET 原生驾驭 AI 新基建实战系列(二):Semantic Kernel 整合对向量数据库的统一支持

    1. 引言 在人工智能(AI)应用开发迅猛发展的今天,向量数据库作为存储和检索高维数据的重要工具,已经成为许多场景(如自然语言处理.推荐系统和语义搜索)的核心组件. 对于.NET生态系统的开发者而言, ...

  4. Window7搭建Kafka环境总结

    1.安装zooeleeper 下载链接:http://mirror.bit.edu.cn/apache/zookeeper/zookeeper-3.4.14/ 安装步骤如下: 1)解压zookeepe ...

  5. cocos3.x creator常见问题及解决办法

     原文地址: cocos3.x creator剪切.动画.物理引擎.碰撞检测等常见问题及解决办法 - 搜栈网 (seekstack.cn)https://www.seekstack.cn/post/4 ...

  6. 【经验】Office|重装后,PPT 2016后失去平滑等功能(解决方式:使用Office Tools Plus重新安装另一版本)

    重装ppt 2016后没有平滑等功能,并且在淡入/淡出中的平滑播放的还是淡入/淡出.本文记录了解决方案. 如何下载带平滑功能的Office? 首先,不能从官网微软服务和订阅下载.因为官网的必须购买才能 ...

  7. AI 在软件测试中的应用:2025 年趋势、工具及入门指南

    引言 人工智能 (AI) 正在深刻地重塑软件开发和质量保证 (QA) 的各个方面.尤其是在软件测试领域,AI 不再仅仅是未来愿景,而是当下正在发生的变革.据世界质量报告(2023-24)指出,高达 7 ...

  8. Linux系列:聊一聊 SystemV 下的进程间共享内存

    一:背景 1. 讲故事 昨天在分析一个 linux 的 dump 时,看到了这么一话警告,参考如下: 0:000> !eeheap -gc *** WARNING: Unable to veri ...

  9. TypeScript实用技巧大杂烩,助你成为真正的全栈工程师

    @charset "UTF-8"; .markdown-body { line-height: 1.75; font-weight: 400; font-size: 15px; o ...

  10. 深度解析JS事件驱动模型:如何理解浏览器中的异步回调和事件循环

    @charset "UTF-8"; .markdown-body { line-height: 1.75; font-weight: 400; font-size: 15px; o ...