React.memo 解决函数组件重复渲染

news2024/12/26 20:48:11

为什么会存在重复渲染?

react 在 v16.8 版本引入了全新的 api,叫做 React Hooks,它的使用与以往基于 class component 的组件用法非常的不一样,不再是基于类,而是基于函数进行页面的渲染,我们把它又称为 functional component

因为 react hook 使用的是函数组件,父组件的任何一次修改,都会导致子组件的函数执行,从而重新进行渲染。

那么下面我们考虑三种情况:

  • 父组件没有 props 传入子组件 props
  • 父组件传入子组件的 props 都是简单数据类型
  • 父组件传入子组件的 props 存在复杂数据类型

React.memo 为高阶组件。它与 React.PureComponent 非常相似。默认只会对复杂类型对象做浅层比较,如果需要控制对比过程我们可以将比较函数作为第二个参数传入:React.memo(MyComp, areEqual)

父组件没有 props 传入子组件 props

在这种情况下,子组件的渲染不需要依赖父组件值的变化,使用 React.memo 包裹子组件,即缓存下子组件。这样,父组件中的数值如何变化,都会使用缓存下来的子组件。

父组件传入子组件的 props 都是简单数据类型

在这种情况下,父组件传入子组件的 props 都是简单数据类型,浅层对比即可判断是否发生了变化,使用 React.memo 包裹子组件,也可以解决重复渲染的问题。

父组件传入子组件的 props 存在复杂数据类型

父组件通过 props 向子组件传值时,可能需要传入复杂类型如 object,以及 function 类型的值。而 memo 子组件进行渲染比对时进行的是浅比较,即使我们传入相同的 objectfunction,子组件也会认为传入参数存在修改,从而子组件重新进行渲染。这个时候仅仅使用 memo 包裹子组件应该没办法解决问题了,是时候用上我们的 useCallback 以及 useMemo 了。

下面我们来看一下 React.memo 的使用。

React.memo 的使用

例如,一个父组件 Home 中渲染了子组件 List,同时 Home 组件还有一个计数器组件,每次点击 count 都会加 1,遇到类似的场景就会出现子组件重复渲染问题,这是因为 React 中当父组件的一个状态改变后,无论和子组件是否有关,子组件都会受到影响进行重新渲染,这也是 React 中默认的一个行为。

函数组件中的解决方案是使用 React.memo() 函数,将需要优化的函数组件传入即可。

import React, { useEffect, useState } from "react";

// 未使用 memo:const List = ({ dataList }) => {
const List = React.memo(({ dataList }) => {
  console.log("List 渲染");

  return (
    <div>
      {dataList.map((item) => (
        <h2 key={item.id}> {item.title} </h2>
      ))}
    </div>
  );
});

const Home = () => {
  const [count, setCount] = useState(0);
  const [dataList, setDataList] = useState([]);

  useEffect(() => {
    const list = [
      { title: "React 性能优化", id: 1 },
      { title: "Node.js 性能优化", id: 2 },
    ];
    setDataList(list);
  }, []);

  return (
    <div>
      <button type="button" onClick={() => setCount(count + 1)}>
        count: {count}
      </button>
      <List dataList={dataList} />
    </div>
  );
};

export default Home;

自定义控制对比过程

函数 React.memo() 还提供了第二个参数 propsAreEqual,用来自定义控制对比过程。

// React.memo() 的 TypeScript 类型描述
function memo<T extends ComponentType<any>>(
  Component: T,
  propsAreEqual?: (
    prevProps: Readonly<ComponentProps<T>>,
    nextProps: Readonly<ComponentProps<T>>
  ) => boolean
): MemoExoticComponent<T>;

使用memo, useMemo, useCallback

useCallback

先来说下,经常使用一些的 useCallback

// 仅使用了memo,父组件传递给子组件的prop为方法,
// 该方法在子组件中被调用,改变了父组件的值,导致父组件重新渲染。
// 又由于父组件重新渲染,传给子组件的方法因其引用地址的不同会被认为有修改,导致子组件出现了不必要的重新渲染。
const Child = memo((props) => {
    console.log('我是一个子组件');
    return (
        <button onClick={props.handleClick}>改变父组件中的年龄</button>
    )
})

const Father = () => {
    console.log('我是一个父组件')
    const [age, setAge] = useState(0);
    return (
        <div>
            <span>`目前的count值为${age}`<span>
            <Child handleClick={() => setAge(age + 1)}/>
        </div>  
    )
}
// 使用了useCallback优化了传递给子组件的函数,只初始化一次这个函数,下次不产生新的函数
const Father = () => {
    console.log('我是一个父组件')
    const [age, setAge] = useState(0);
    return (
        <div>
            <span>`目前的年龄为${age}`<span>
            <Child handleClick={useCallback(() => setAge(age + 1), [])}/>
        </div>
    )
}

注意:在 useCallback 的第二个参数处要传入正确的依赖值,否则 useCallback 就不会重新执行,其中使用的变量就还是之前的值,useMemo也是如此。

我们在方法中可能会使用一些组件中但是存在方法外的参数,我们一定要将这些参数放入依赖项中,否则会一直使用缓存的方法,里面的外部参数也会一直是旧值。

useMemo

// 使用了memo以及useCallback,我们会发现更新属性profile为对象时,
// 尽管子组件只改变了age的值且子组件并没有使用age字段,子组件还是执行了。
// 这是因为在父组件更新其他状态的情况下,子组件的profile作为复杂类型,
// 仅仅进行浅比较会被认为存在修改,从而会一直重新渲染改变,导致子组件函数一直执行,这也是不必要的性能浪费。
// 解决这个问题,就需要在profile属性上使用useMemo了
const Child = memo((props) => {
    console.log('我是一个子组件');
    const {profile, handleClick} = props;
    return (
        <div>
           <div>{`父组件传来的用户信息:姓名${profile.name}, 性别${profile.gender}`}</div>
           <button onClick={handleClick}>改变父组件age</button>
        </div>
    )
})

const Father = () => {
    console.log('我是一个父组件')
    const [age, setAge] = useState(0);
    const [name, setName] = useState('张三男');
    const 
    return (
        <div>
            <span>`目前的年龄为${age}`<span>
            <Child
                profile={{name, gender: name.indexOf('男') > -1 ? '男' : '女' }}
                handleClick={useCallback(() => setAge(age + 1), [])}
            />
        </div>
        
    )
}
// 使用useMemo,返回一个和原对象一样的对象,第二个参数是依赖性,仅当name发生改变的时候,才产生一个新的对象,注意:依赖项千万要填写正确,否则name改变时,profile依旧使用旧值,就会产生错误

const Father = () => {
    console.log('我是一个父组件')
    const [age, setAge] = useState(0);
    const [name, setName] = useState('张三男');
    const 
    return (
        <div>
            <span>`目前的年龄为${age}`<span>
            <Child
                profile={useMemo(() => ({
                    name, 
                    gender: name.indexOf('男') > -1 ? '男' : '女' }), [name])
                }
                handleClick={useCallback(() => setAge(age + 1), [])}
            />
        </div>
        
    )
}

React.memo 无效情况

第一种

React.memo 对普通的引用类型是无效的。例如,在 List 组件增加 user 属性,即使使用了 React.memo() ,每次点击 count, List 组件还会重复渲染。

const Home = () => {
  const user = {name: '哈哈'};
  ...

  return (
    <div>
      <List dataList={dataList} user={user} />
    </div>
  );
};

React.memo() 结合使用时,普通引用类型对象需要通过 useMemouseState 处理,来避免组件的重复渲染。

const user = useMemo(() => ({ name: "哈哈" }), []);
const [user] = useState({ name: "哈哈" });

第二种

函数组中包括了一些 Hooks 例如 useStateuseContext,当上下文发生变化时,组件也同样会重新渲染,React.memo 在这里仅比较 props。上面例子中,如果把 button 组件放到 List 组件里,每次点击,List 也还是会被重新渲染。

const List = React.memo(({ dataList }) => {
  console.log("List 渲染");
  const [count, setCount] = useState(0);

  return (
    <div>
      <button type="button" onClick={() => setCount(count + 1)}>
        List count: {count}
      </button>
      {dataList.map((item) => (
        <h2 key={item.id}> {item.title} </h2>
      ))}
    </div>
  );
});

总结

React.memo() 是一个高阶组件,接收一个组件并返回一个新组件。它会记忆组件上次的 Props,同下次需要更新的 Props 做 “浅对比”,如果相同就不做更新,只有在不同时才会重新渲染。如果你的组件存在一些耗时的计算,每次重新渲染对页面性能显然是糟糕的,这时 React.memo() 对你来说也许是一个好的选择。并不是所有的组件都要引入 React.memo(),自身浅对比这个过程也会有一些消耗,如果没有特殊需求,也不一定非要使用。

  1. 子组件没有从父组件传入的 props 或者传入的 props 仅仅为简单数值类型使用 memo 即可
  2. 子组件有从父组件传来的方法时,在使用 memo 的同时,使用 useCallback 包裹该方法,传入方法需要更新的依赖值
  3. 子组件有从父组件传来的对象和数组等值时,在使用 memo 的同时,使用 useMemo 以方法形式返回该对象,传入需要更新的依赖值

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/389256.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

id函数 / 可变类型变量 / 不可变类型变量 / +=操作

前言 再说正文之前&#xff0c;需要大家先了解一下对象&#xff0c;指针和引用的含义&#xff0c;不懂得同学可以参考我上一篇博客“(12条消息) 引用是否有地址的讨论的_xx_xjm的博客-CSDN博客” 正文 一&#xff1a;python中一切皆对象 “python中一切皆对象”这句话我相信…

【LeetCode】剑指 Offer(15)

目录 题目&#xff1a;剑指 Offer 32 - II. 从上到下打印二叉树 II - 力扣&#xff08;Leetcode&#xff09; 题目的接口&#xff1a; 解题思路&#xff1a; 代码&#xff1a; 过啦&#xff01;&#xff01;&#xff01; 题目&#xff1a;剑指 Offer 32 - III. 从上到下打…

分子模拟—Ovito渲染案例教程

♡\heartsuit♡♠\spadesuit♠《分子模拟—Ovito渲染案例教程》♡\heartsuit♡♠\spadesuit♠&#xff1a; 说明&#xff1a; 本博文主要介绍专栏 《分子模拟—Ovito渲染案例教程》\rm 《分子模拟—Ovito渲染案例教程》《分子模拟—Ovito渲染案例教程》, 依托于实际的物理问题…

高数:数列的收敛

数列特点无限个数特定顺序数列和集合区别集合可以乱序&#xff0c;数列不行集合出现重复元素依然相同&#xff0c;数列出现新的重复元素就不相等[1&#xff0c;2&#xff0c;3&#xff0c;4][1&#xff0c;2&#xff0c;3&#xff0c;3&#xff0c;4]对集合来说相等&#xff0c…

【大数据实时数据同步】超级详细的生产环境OGG(GoldenGate)12.2实时异构同步Oracle数据部署方案(上)

系列文章目录 【大数据实时数据同步】超级详细的生产环境OGG(GoldenGate)12.2实时异构同步Oracle数据部署方案(上) 【大数据实时数据同步】超级详细的生产环境OGG(GoldenGate)12.2实时异构同步Oracle数据部署方案(中) 文章目录系列文章目录前言一、数据库配置信息二、下载OGG1…

LQB手打代码,DS1302基础代码01

一&#xff0e;描述 DS1302时钟芯片是由美国DALLAS公司推出的具有涓细电流充电能力的低功耗实时时钟芯片。它可以对年、月、日、周、时、分、秒进行计时&#xff0c;且具有闰年补偿等多种功能。DS1302芯片包含一个用于存储实时时钟/日历的 31 字节的静态 RAM&#xff0c;可通过…

docker 运行花生壳实现内外网穿透

环境&#xff1a;centos 7 ,64位 1、创建一个指定的文件夹作为安装示例所用&#xff0c;该示例文件夹为“hsk-nwct”。“hsk-nwct”内创建“app”文件夹作为docker容器挂载出来的文件。 2、在“app”内下载花生壳linux安装包&#xff0c;下载花生壳应用&#xff1a;花生壳客户…

最新版本vue3+vite重构尚品汇(解决接口问题)第21-50集

第21集&#xff0c;第22集&#xff1a;照敲就行&#xff0c;引入概念。 第23集&#xff1a;防抖概念&#xff1a;前面所有的触发被取消&#xff0c;最后一次执行在规定的时间之后才会触发&#xff0c;只会执行一次。Lodash插件里面封装了函数的防抖和节流的业务。用到lodash确实…

SpringAOP——基础知识

AOP AOP全称是Aspect Oriented Programming 即面向切面编程&#xff0c;是对一类统一事务的集中处理 例如&#xff0c;我们的网页许多地方都需要进行登陆验证&#xff0c;这时就需要在很多地方添加重复的验证代码&#xff0c;而AOP可以集中配置需要登陆验证的地方&#xff0c;…

简单认识: 数据库 zhi 操作(MySQL为例)

目录 前言 一&#xff0c;库 二&#xff0c;站在巨人的肩膀上理解 库 的操作 下节预告&#xff1a; 前言 今天是库库森日记第n……篇之数据库篇一 库 库 库~~~~的~操~作。 如果看完这篇文章&#xff0c;你觉得数据库从简单的创建库&#xff0c;表&#xff0c;修改…

世界那么大,你哪都别去了,来我带你了解CSS3 (一)

文章目录‍❤️‍&#x1f525;CSS简介‍‍‍‍❤️‍&#x1f525;CSS选择器❣️选择器一❣️选择器二❣️关系选择器‍❤️‍&#x1f525;CSS属性❣️字体属性❣️背景属性❣️文本属性❣️表格属性‍❤️‍&#x1f525;CSS简介 CSS概念 CSS&#xff08;Cascading Style Sh…

JWT续期问题,ChatGPT解决方案

JWT&#xff08;JSON Web Token&#xff09;通常是在用户登录后签发的&#xff0c;用于验证用户身份和授权。JWT 的有效期限&#xff08;或称“过期时间”&#xff09;通常是一段时间&#xff08;例如1小时&#xff09;&#xff0c;过期后用户需要重新登录以获取新的JWT。然而&…

数据结构基础之链表

目录 前言 1、什么是链表 2、添加元素 3、虚拟头结点 4、查询&修改元素 5、删除元素 附&#xff1a;完整代码 前言 又到周末了&#xff0c;修整了一天&#xff0c;继续来写点东西吧&#xff0c;今天&#xff0c;我们来学习数据结构中的另一种基础的数据结构——链表…

jsp进阶

文章目录jsp进阶内容回顾JSP 的九大内置对象内置对象的创建九大内置对象详解四大作用域对象四大作用域范围总结EL 进阶JSTL 标准标签库JSTL 核心标签jsp进阶 内容回顾 jsp 创建 jsp 的工作原理&#xff1a;翻译 --> 编译 --> 运行 翻译&#xff1a;第一次访问 jsp 页面…

Redis简单笔记

1 为什么需要Redis 数据分冷热&#xff0c;将热数据存储到内存中 2 Redis应用案例 2.1 连续签到 2.1.1 String数据结构 可以存储字符串、数字、二进制数据通常和expire配合使用场景:存储计数、Session2.2 消息通知 用list作为消息队列 使用场景:消息通知。 例如当文章更新时…

机器学习模型的可解释性算法汇总!

模型可解释性汇总简 介目前很多机器学习模型可以做出非常好的预测&#xff0c;但是它们并不能很好地解释他们是如何进行预测的&#xff0c;很多数据科学家都很难知晓为什么该算法会得到这样的预测结果。这是非常致命的&#xff0c;因为如果我们无法知道某个算法是如何进行预测&…

巴别塔——问答平台调研

项目内容这个作业属于哪个课程2023 年北航软件工程这个作业的要求在哪里个人作业-软件案例分析我在这个课程的目标是了解软件工程的方法论、获得软件项目开发的实践经验、构建一个具有我的气息的艺术品这个作业在哪个具体方面帮助我实现目标对于“程序员是什么”这个问题有了一…

【ChatAug: Leveraging ChatGPT for Text Data Augmentation 论文精读】

ChatAug: Leveraging ChatGPT for Text Data Augmentation 论文精读InformationAbstract1 Introduction2 RELATED WORK2.1 Data Augmentation2.2 Few-shot Learning2.3 Very Large Language Models2.4 ChatGPT: Present and Future3 DATASET3.1 Symptoms Dataset3.2 PubMed20k …

Qt扫盲-CMake 使用概述

CMake 使用概述一、概述二、创建Qt CMake 项目三、简单介绍1. 引入Qt的库2.Qt CMake 引入第三方库3. Qt CMake 项目目录四、使用案例一、概述 CMake是一个简化跨不同平台开发项目的构建过程的工具。对C来说其实就是生成一个文件&#xff0c;文件里面描述了&#xff0c;怎么组织…

【MyBatis】MyBatis操作数据库

MyBatis操作数据库 文章目录MyBatis操作数据库:one:什么是MyBatis:two:创建SSM项目引入依赖配置文件设置MyBatis底层逻辑:three:实现CRUD功能查询全列查询带参数的查询新增获取自增主键删除更新:four:参数占位符&#xff1a;#{}和${}不支持String参数问题${}使用场景&#xff1…