【React】memo在props不变的情况下不再重复渲染组件

前言

组件中状态(State)发生改变会导致该组件重新渲染,其中的子组件也会被重新渲染。如果子组件中并未使用该状态(State),重复渲染会导致无效的性能损耗。

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

基本使用

使用 memo 将组件包装起来,以获得该组件的一个 记忆化 版本。通常情况下,只要该组件的 props 没有改变,这个记忆化版本就不会在其父组件重新渲染时重新渲染。

import { memo } from “react”

memo(Component, arePropsEqual?)

参数

  • Component:要进行记忆化的组件。memo 不会修改该组件,而是返回一个新的、记忆化的组件。它接受任何有效的 React 组件,包括函数组件和 forwardRef 组件。
  • 可选参数 arePropsEqual:一个函数,接受两个参数:组件的前一个 props 和新的 props。如果旧的和新的 props 相等,即组件使用新的 props 渲染的输出和表现与旧的 props 完全相同,则它应该返回 true。否则返回 false。通常情况下,你不需要指定此函数。默认情况下,React 将使用 Object.is 比较每个 prop。

返回值

memo 返回一个新的 React 组件。它的行为与提供给 memo 的组件相同,只是当它的父组件重新渲染时 React 不会总是重新渲染它,除非它的 props 发生了变化。

案例

未使用memo

Head组件中并未使用count,但setCount后,也发生了重新渲染。

import React, { useState } from "react"

function Head() {
  return <div>Head,{Math.random()}</div>
}

function App() {
  const [count, setCount] = useState(1)
  return (
    <>
      <div>App,count={count}</div>
      <button
        onClick={() => {
          setCount(count + 1)
        }}
      >
        添加
      </button>
      <Head></Head>
    </>
  )
}

点击添加前

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

点击后,可以发现setState导致子组件也重新渲染。
在这里插入图片描述

如果当子组件中逻辑复杂或者父组件会频繁重新渲染,就会带来性能消耗,可以使用memo来对此进行优化。

使用memo

import React, { useState, memo } from "react"

const Head = memo(function Head() {
  return <div>Head,{Math.random()}</div>
})

function App() {
  const [count, setCount] = useState(1)
  return (
    <>
      <div>App,count={count}</div>
      <button
        onClick={() => {
          setCount(count + 1)
        }}
      >
        添加
      </button>
      <Head></Head>
    </>
  )
}

点击前

在这里插入图片描述

多次点击发现,子组件并不会重新渲染

在这里插入图片描述

如果子组件props也接收了count这个状态,那么还会再次渲染

注意

props复杂数据类型时,setCount后,App组件重新渲染,此时list的地址发生改变,由于Object.is对比的是两个复杂数据类型的地址,所以子组件的props会被视为已发生改变,导致再次渲染,此时需要使用 useMemo 对计算结果进行缓存。

import React, { useState, memo } from "react"

const Head = memo(function Head() {
  return <div>Head,{Math.random()}</div>
})

function App() {
  const [count, setCount] = useState(1)
  const [msg, setMsg] = useState("Hello World")
  const list = [msg.toLowerCase(), msg.toUpperCase()]
  return (
    <>
      <div>App,count={count}</div>
      <button
        onClick={() => {
          setCount(count + 1)
        }}
      >
        添加
      </button>
      <Head list={list}></Head>
    </>
  )
}

使用 useMemo 缓存

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

const Head = memo(function Head() {
  return <div>Head,{Math.random()}</div>
})

function App() {
  const [count, setCount] = useState(1)
  const [msg, setMsg] = useState("Hello World")
  const list = useMemo(()=>[msg.toLowerCase(), msg.toUpperCase()],[msg])
  return (
    <>
      <div>App,count={count}</div>
      <button
        onClick={() => {
          setCount(count + 1)
        }}
      >
        添加
      </button>
      <Head list={list}></Head>
    </>
  )
}

useMemo可以将结果进行缓存,其第一个参数的返回值为需要缓存的数据,第二个参数是一个依赖数组,只有依赖项发生改变时,才会重新计算,否则一直使用缓存值。

props函数时,useMemo的写法可读性较差,可以使用useCallback对函数进行缓存。

import React, { useState, memo, useMemo, useCallback } from "react"

const Head = memo(function Head() {
  return <div>Head,{Math.random()}</div>
})

function App() {
  const [count, setCount] = useState(1)
  const [msg, setMsg] = useState("Hello World")
  // 使用useMemo
  const fn = useMemo(
    () => () => {
      console.log("fn")
    },
    [msg]
  )
  // 使用useCallback
  const fn = useCallback(() => {
    console.log("fn")
  }, [msg])
  return (
    <>
      <div>App,count={count}</div>
      <button
        onClick={() => {
          setCount(count + 1)
        }}
      >
        添加
      </button>
      <Head fn={fn}></Head>
    </>
  )
}

总结

被memo函数处理过的组件只有当本身的props改变之后才会重新渲染。

memo源码

源码

function memo(type, compare) {
  // 核心代码
  return {
    $$typeof: REACT_MEMO_TYPE,
    type: type,
    compare: compare === undefined ? null : compare
  };
}

memo 的源码比较简单,在返回一个记忆化组件的同时,给组件添加 $$typeof 标记。在创建 Fiber 时,会根据 $$typeof 的值,给 fiber.tag 赋值为 MemoComponent(MemoComponent=14)。

工作流

当 state(可以是任何组件的 state)改变时,React 会从根节点开始遍历 Fiber 树,在这个过程中会对每一个 Fiber 节点调用 beginWork 方法。当 tag 为 MemoComponent 时,会调用 updateMemoComponent 方法。当我们在使用 memo 没有传入第二个参数时,会调用默认的 shallowEqual 对 prevProps 和 nextProps 进行浅比较,当结果为 true 时,组件就不会重新渲染。

function beginWork(...) {
  // ...
  switch (workInProgress.tag) {
    case MemoComponent: {
      return updateMemoComponent(...);
    }
  }
}

function updateMemoComponent(...) {
  // ...
  if (updateExpirationTime < renderExpirationTime) {
    // Default to shallow comparison
    var compare = Component.compare;
    compare = compare !== null ? compare : shallowEqual;
    if (compare(prevProps, nextProps) && current$$1.ref === workInProgress.ref) {
      return bailoutOnAlreadyFinishedWork(...);
    }
  }
  var newChild = createWorkInProgress(...);
  return newChild;
}
  • 9
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

田本初

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值