浅谈React.memo函数

news/2024/7/15 19:20:58 标签: react.js, javascript, 前端

背景

在React中,组件渲染的是最常有的事情。但是,有部分的渲染是不必要的,是可以避免的。

在react的一般规则中,只有父组件的某一个状态改变,父组件下面所有的子组件不论是否使用了该状态,都会进行重新渲染。

显然,对于没有用到被改变的那个状态的组件来说,重新渲染是完全没有必要的。所以,React.memo就诞生了。

什么是 React.memo

React.memo 是一个高阶组件,它可以用来包装一个函数组件(Functional Component)并返回一个新的组件。这个新组件会对传入的 props 进行浅比较(Shallow Comparison),如果发现传入的 props 没有发生变化,则直接返回上一次渲染的结果,从而避免不必要的重复渲染过程,提升了组件的渲染性能。否则重新渲染组件。

React.memo 和 React.PureComponent 类似,都可以用于优化组件的性能。但是它们之间也存在着很大的区别,React.PureComponent 只适用于 class 组件,而 React.memo 则适用于所有函数组件。

React特性:父组件中状态的改变会让所有的子组件重新渲染

比如:

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

export const EgOfMemo: React.FC = () => {
  // 传入子组件的状态
  const [name, setName] = useState('lvxiaobu')
  // 没有传入子组件的状态,但是当age更新后,父组件会重新渲染,所有的子组件无论是否用到age,也全部会重新渲染
  const [age, setAge] = useState(0)

  return (
    <div>
      {/* 点击按钮,子组件Child会重新渲染 */}
      <button onClick={() => setAge(1)}> 点击修改年龄</button>
      <Child name={name} />
    </div>
  )
}

只要父组件的状态改变,所有的子组件不论是否使用到了被改变的那个state都会被重新渲染。
显然这种渲染是完全没必要的。我又没有使用被改变的那个state,我本身本身也没有什么视图需要更新,根本没必要重新渲染。

在阻止重新渲染这个需求的基础上,诞生了memo函数,memo是react的一种缓存技术,这个函数可以检测从父组件接收的props,并且在父组件改变state的时候比对这个state是否是本组件在使用,如果不是,则不会重新渲染。

用法

使用 React.memo 很简单,只需要将要优化的函数组件作为参数传递给 React.memo 即可,例如:

javascript">import React, { memo } from 'react';

function Child(props) {
  // 子组件的具体实现逻辑
}

export default memo(Child);

在上面的例子中,我们使用 memo 包装了一个名为Child的函数组件,并导出了一个新的组件。此时,这个新组件会对传入的 props 进行浅比较,以便在 props 没有发生变化时直接使用上一次渲染的结果。
像这样,这Child就是被缓存成功了。下次当父组件中无关它的state(状态)被更新时候,Child组件就不会重新渲染了。
总之就是,如果当前组件被memo保护,那么当前组件的props不变,则组件不进行重新渲染。这样,我们合理的使用memo就可以为我们的项目带来很大的性能优化。

注意事项

虽然 React.memo 可以帮助我们优化组件的性能和渲染速度,但也存在着一些需要注意的事项。

1、首先,由于 React.memo 只进行浅比较,因此如果 props 中包含了引用类型的数据(如数组或对象),那么即使这些引用类型的数据没有发生变化,也会触发重新渲染。因此,需要特别注意在 props 中传递引用类型的数据时,要确保它们不会频繁地发生变化,才能获得最佳的性能表现。

import React, { useState } from "react";
import { EgOfMemo } from '../../components'

export const Home: React.FC = () => {
  const [name, setName] = useState('');
  const [list, setList] = useState([1,2,3])
 
  const modifyName = () => {
  	setName('lvxiaobu')
    list.push(4)
    // list更新了,但子组件没有重新渲染
    console.log(1, list)
  }

  return (
    <>
      <div>{name}</div>
      <button onClick={modifyName}>点击</button>
      <EgOfMemo list={list}  />
    </>
  )
}

上面传入了一个list数组进去子组件,子组件内部是被memo缓存了的。点击按钮的时候,如果我们往list这个数组中push一个4,那么子组件中的props改变了,理论上来说,子组件应该重新渲染了。但实际上并不会。

这是为什么呢?因为memo的保护是对props做一个浅比较(只比较栈,不比较堆)

而数组的使用push()方法看似是变了。但变的只是堆中的数据,存在与栈中的地址依然不会改变。memo是检测不到的。所以,使用push等不能返回一个新数组的方法,均无法触发memo的更新机制。

想要改变数组也能让子组件更新,有方法。
就像刚刚说的,需要让memo检测到数组栈地址的变化。要栈地址变化的话,只要返回一个全新的数组就好了,比如:

setList([...list, 4]); //这样才可以,创建一个新数组,再在里面解构旧数组,往后面追加 1

这样,就可以既修改数组,又触发组件更新了。
2、一旦传入给子组件的是函数类型,那么子组件使用memo包裹也没法让它避免没有意义的渲染

import React, { useState } from "react";
import { EgOfMemo } from '../../components'

export const Home: React.FC = () => {
  let [name, setName] = useState('1');
  const [list, setList] = useState([1,2,3])

  const modifyName = () => {
    setName(name + '!')
  }
  // 父组件状态更新就会重新渲染,每次重新渲染foo函数都会被重新构建,并返回新的内存地址给foo,所以即使子组件用了memo包裹,子组件依然认为props的内存地址更新了,故子组件会重新渲染
  const foo = () => {
    console.log('foo')
  }

  return (
    <>
      <div>{name}</div>
      <button onClick={modifyName}> 点击修改名字 </button>
      <EgOfMemo list={list} foo={foo} />
    </>
  )
}

上面代码解读一下:父组件状态更新就会重新渲染,每次重新渲染foo函数都会被重新构建,并返回新的内存地址给foo,所以即使子组件用了memo包裹,子组件依然认为props的内存地址更新了,故子组件会重新渲染。
也就是说,一旦传入给子组件的是函数类型,那么子组件使用memo包裹也没法让它避免没有意义的渲染,那应该怎么做呢?使用useCallback函数配合,下节博客会介绍!

3、不能每个组件都用memo包裹:如果真的每个组件都有被缓存的必要而且不会给项目带来破坏性问题,为什么react不直接把memo设为默认的呢。当然是因为每个都缓存的话,会给项目带来毁灭性的问题咯。

我们需要知道的是,缓存也需要成本。如果每个组件都进行缓存,会给浏览器带来非常非常大的负担。

所以在平常项目中,我们需要挑选一些经常被使用,经常会被重新渲染的组件去有目标的缓存他。而不是每一个组件都缓存一下。切记切记。

在某些情况下,使用 React.memo 可能会导致性能反而变差,例如当组件的渲染开销很小、props 变化频繁或者组件本身就是高阶组件时。 因此,在使用 React.memo 时,需要根据具体的场景进行评估和调整,以获得最优的性能表现。

最后,需要注意的是,React.memo 并不是万能的优化工具,它只能针对某些特定场景进行优化,而对于一些更复杂的性能问题,还需要使用其他更加专业的工具和技术进行优化。

总结

  1. 父组件中state(状态)改变,不受memo保护的子组件也会重新渲染
  2. 被memo函数包起来的组件只有本身的props被改变之后才会重新渲染
  3. memo只能进行浅比较来校验决定是否触发重新渲染。所以改变数组(对象)类型的props的时候记得返回一个全新的数组(对象)
  4. memo不是项目中所有的组件都需要包一下。包的太多反而会起反效果,我们需要选择那些经常被重新渲染的组件有选择性的去缓存。

React.memo 是 React 中一个很实用的工具,可以帮助我们优化组件的性能和渲染速度。它通过记忆组件的 props,并在下一次渲染时只有当 props 发生变化时才会重新渲染组件,从而大大提高了组件的渲染性能!


http://www.niftyadmin.cn/n/386076.html

相关文章

数据库 期末复习(4) 概念数据库的设计

参考资料 :邹老师数据库课件 程老师数据库课件 战老师数据库课件 第一部分 为啥要引入概念数据库 感觉只有一个重点 实体联系模型----ER模型 第二部分-----实体联系模型 这个例子可以全看完之后再来看 举个例子:根据COMPANY数据库的需求来构造数据库模式&#xff1a;The com…

传统图形学对nerf的对比与应用落地

作者今年参加了China3DV的盛会&#xff0c;大会的发表、线下讨论、学者、工业界等等的交流着实对于Nerf有了更深的思考&#xff0c;以下是作者的抛砖引玉&#xff0c;如有不当之处敬请指出~ 传统图形学与nerf的简介&#xff1a; 传统图形学&#xff1a;显示表达几何表达方式&…

树莓派初体验:开机啦

感谢大佬的赞助&#xff0c;这玩意是真的贵哇&#xff0c;呜呜呜呜呜呜&#xff0c;根本买不起 一、烧录系统 需要&#xff1a;SD卡&#xff08;推荐16G&#xff09;、读卡器&#xff08;推荐高速读卡器&#xff09; 进入官网&#xff1a;https://www.raspberrypi.com/softwa…

Are Emergent Abilities of Large Language Models a Mirage?

Paper name Are Emergent Abilities of Large Language Models a Mirage? Paper Reading Note Paper URL: https://arxiv.org/pdf/2304.15004.pdf Video URL: https://www.youtube.com/watch?vhZspGdApDIo TL;DR 2023 年斯坦福的研究&#xff0c;探索大语言模型表现出涌…

Java性能权威指南-总结4

Java性能权威指南-总结4 Java性能调优工具箱操作系统的工具和分析CPU运行队列磁盘使用率网络使用率 Java监控工具基本的VM信息 Java性能调优工具箱 操作系统的工具和分析 CPU运行队列 快速小结 检查应用性能时&#xff0c;首先应该审查CPU时间。优化代码的目的是提升而不是…

【javaEE】计算机网络原理初始

目录 1、网络发展史 1.1、独立模式 1.2、网络互连 1.2.1、局域网&#xff08;LAN&#xff09; 1.2.2、广域网&#xff08;WAN&#xff09; 1.2.3、广域网和局域网的区别 1.2.4、局域网组建网络的方式 &#xff08;了解&#xff09; &#xff12;、网络通信基础 2.1、I…

RK3588平台开发系列讲解(驱动基础篇)中断相关函数

平台内核版本安卓版本RK3588Linux 5.10Android 12文章目录 一、获取中断号相关函数二、申请中断函数三、free_irq 函数四、中断处理函数五、中断使能和禁止函数沉淀、分享、成长,让自己和他人都能有所收获!😄 📢 Linux 中断有专门的中断子系统,其实现原理很复杂,但是驱…

Vue.js 比较重要知识点总结二

概述 vue3 组合式API生命周期钩子函数有变化吗&#xff1f;Composition API 与 Options API 有什么区别&#xff1f;watch 和 watchEffect 的区别&#xff1f;vue2 如何升级到 vue3 ? vue3 组合式API生命周期钩子函数有变化吗&#xff1f; 选项式API 和 组合式API 生命周期…