一句话概括

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. 舵机SG90详解

    舵机,也叫伺服电机,在嵌入式开发中,舵机作为一种常见的运动控制组件,具有广泛的应用.其中,SG90 舵机以其高效.稳定的性能特点,成为了许多工程师和爱好者的首选,无论是航模.云台.机器人.智能小车中都 ...

  2. Web前端入门第 29 问:CSS 盒模型:网页布局的基石

    在 Web 网页开发中,盒模型(Box Model) 是 CSS 的核心概念,它决定了每个 HTML 元素在页面中占据的空间和布局方式. 无论是文本.图片还是按钮,浏览器都会将它们视为一个矩形盒子,并 ...

  3. 收藏破10w的教程!用DeepSeek做可视化:5个案例搞定工作汇报/论文/自媒体,一键生成(保姆级喂饭,附全套模板)

    大家好,我是狂师. DeepSeek作为今年爆火的AI工具,已经被广泛用于各种办公或自媒体写作创作场景,比如可以用DeepSeek辅助帮我们生成各种代码,如Python.Java.SQL.JavaSc ...

  4. Web前端入门第 38 问:CSS flex 弹性盒子与 grid 网格布局区别及应用场景

    弹性盒子又称为 Flexbox,然而我更喜欢 flex 的叫法. flex 弹性盒子和 grid 网格布局作为前端开发中两把利器,它们的分界线没那么明显,虽然按照 MDN 的说法 flex 多用于一维 ...

  5. Asp.net core 少走弯路系列教程(四)JavaScript 学习

    前言 新人学习成本很高,网络上太多的名词和框架,全部学习会浪费大量的时间和精力. 新手缺乏学习内容的辨别能力,本系列文章为新手过滤掉不适合的学习内容(比如多线程等等),让新手少走弯路直通罗马. 作者认 ...

  6. pandas 将excle两行或多行文本合并为一行

    原有excle 目的: # j加载另一份数据源 import pandas as pd import xlrd import time from xlutils.copy import copy fr ...

  7. 【Linux】Linux内核模块开发

    Linux内核模块开发 零.关于 1.概述 最近在学习Linux相关的东西,学习了U-Boot的编译,Linux的编译,能够在开发板上运行自己编译的U-Boot和Linux了,那么接下来就是在自己编译 ...

  8. 【HUST】网安|编译原理实验|实验四攻略

    [实验代码及报告地址:Gitee传送门](已关闭传送大门,原因是抄袭过多,如需参考,请直接看博客,虽然下一届内容会变了) 不擅长写报告昂,很多地方能省全省了. 助力来年编译原理加大难度!(hhh) M ...

  9. ASP.NET Core Razor融合JS库Demo

    cshtml.cs using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; namespace Razor ...

  10. 高德地图 MCP,可用 Java SolonMCP 接入(支持 java8, java11, java17, java21)

    1.MCP技术概述 1.1 什么是 MCP MCP (Model Control Protocol) 是一种允许大模型与外部工具交互的协议,高德地图基于此协议提供了地图服务能力,使 AI 大模型能够直 ...