React.memo() 和 useMemo() 的用法是什么,有哪些区别?

在软件开发中,通常痴迷于性能提升以及如何使我们的应用程序执行得更快,从而为用户提供更好的体验。

Memoization 是优化性能的方法之一。 在本文中,将探讨它在 React 中的工作原理。

什么是 memoization?

在解释这个概念之前,先来看一个简单的斐波那契程序:

function fibonacci(n){
  return (n < 2) ? n : fibonacci(n-1) + fibonacci(n-2);
}

显然这个算法缓慢的令人绝望,因为做了非常多的冗余计算,这个时候memoization就可以派上用场了!

简单来说,memoization 是一个过程,它允许缓存递归/昂贵的函数调用的值,以便下次使用相同的参数调用函数时,返回缓存的值而不必重新计算函数。

这确保了应用程序运行得更快,因为通过返回一个已经存储在内存中的值来避免重新执行函数需要的时间。

为什么在 React 中使用 memoization?

在 React 函数组件中,当组件中的 props 发生变化时,默认情况下整个组件都会重新渲染。

换句话说,如果组件中的任何值更新,整个组件将重新渲染,包括尚未更改其 values/props 的函数/组件。

看一个发生这种情况的简单示例。 构建一个基本的应用程序,告诉用户哪种酒最适合与它们选择的奶酪搭配。

从设置两个组件开始。 第一个组件将允许用户选择奶酪。 然后它会显示最适合该奶酪的酒的名称。 第二个组件将是第一个组件的子组件。 在这个组件中,没有任何变化。 我们将使用这个组件来跟踪 React 重新渲染的次数。

注意,本示例中使用的 classNames 来自 Tailwind CSS。

下面是我们的父组件:<ParentComponent />

// components/parent-component.js
import Counts from "./counts";
import Button from "./button";
import { useState, useEffect } from "react";
import constants from "../utils";
const { MOZARELLA, CHEDDAR, PARMESAN, CABERNET, CHARDONAY, MERLOT } = constants;

export default function ParentComponent() {
  const [cheeseType, setCheeseType] = useState("");
  const [wine, setWine] = useState("");
  const whichWineGoesBest = () => {
    switch (cheeseType) {
      case MOZARELLA:
        return setWine(CABERNET);
      case CHEDDAR:
        return setWine(CHARDONAY);
      case PARMESAN:
        return setWine(MERLOT);
      default:
        CHARDONAY;
    }
  };
  useEffect(() => {
    let mounted = true;
    if (mounted) {
      whichWineGoesBest();
    }
    return () => (mounted = false);
  }, [cheeseType]);

  return (
    <div className="flex flex-col justify-center items-center">
        <h3 className="text-center dark:text-gray-400 mt-10">
          Without React.memo() or useMemo()
        </h3>
      <h1 className="font-semibold text-2xl dark:text-white max-w-md text-center">
        Select a cheese and we will tell you which wine goes best!
      </h1>
      <div className="flex flex-col gap-4 mt-10">
        <Button text={MOZARELLA} onClick={() => setCheeseType(MOZARELLA)} />
        <Button text={CHEDDAR} onClick={() => setCheeseType(CHEDDAR)} />
        <Button text={PARMESAN} onClick={() => setCheeseType(PARMESAN)} />
      </div>
      {cheeseType && (
        <p className="mt-5 dark:text-green-400 font-semibold">
          For {cheeseType}, <span className="dark:text-yellow-500">{wine}</span>{" "}
          goes best.
        </p>
      )}
      <Counts />
    </div>
  );
}

第二个组件是 <Counts /> 组件,它跟踪整个 <Parent Component /> 组件重新渲染的次数。

// components/counts.js
import { useRef } from "react";
export default function Counts() {
  const renderCount = useRef(0);
  return (
    <div className="mt-3">
      <p className="dark:text-white">
        Nothing has changed here but I've now rendered:{" "}
        <span className="dark:text-green-300 text-grey-900">
          {(renderCount.current++)} time(s)
        </span>
      </p>
    </div>
  );
}

下面的例子是点击奶酪名字时的效果:

<ParentComponent /> 中的 <Counts /> 组件计算了因 <ParentComponent /> 的更改而强制 <Counts /> 组件重新渲染的次数。

目前,单击奶酪名字将更新显示下面的奶酪名字以及酒名。 除了 <ParentComponent /> 会重新渲染,<Counts /> 组件也会重新渲染,即使其中的任何内容都没有改变。

想象一下,有一个组件显示数以千计的数据,每次用户单击一个按钮时,该组件或树中的每条数据都会在不需要更新时重新渲染。 这就是 React.memo() 或 useMemo() 为我们提供性能优化所必需的地方。

现在,探索 React.memo 以及 useMemo()。 之后将比较它们之间的差异,并了解何时应该使用一种而不是另一种。

什么是 React.memo()?

React.memo() 随 React v16.6 一起发布。 虽然类组件已经允许您使用 PureComponent 或 shouldComponentUpdate 来控制重新渲染,但 React 16.6 引入了对函数组件执行相同操作的能力。

React.memo() 是一个高阶组件 (HOC),它接收一个组件A作为参数并返回一个组件B,如果组件B的 props(或其中的值)没有改变,则组件 B 会阻止组件 A 重新渲染 。

将采用上面相同的示例,但在 <Counts /> 组件中使用 React.memo()

需要做的就是用 React.memo() 包裹<Counts /> 组件,如下所示:

import { useRef } from "react";
function Counts() {
  const renderCount = useRef(0);
  return (
    <div className="mt-3">
      <p className="dark:text-white">
        Nothing has changed here but I've now rendered:{" "}
        <span className="dark:text-green-300 text-grey-900">
          {(renderCount.current ++)} time(s)
      </span>
      </p>
    </div>
  );
}
export default React.memo(Counts);

现在,当通过单击选择奶酪类型时,<Counts /> 组件将不会重新渲染。

什么是 useMemo()?

React.memo() 是一个 HOC,而 useMemo() 是一个 React Hook。 使用 useMemo(),可以返回记忆值来避免函数的依赖项没有改变的情况下重新渲染。

为了在我们的代码中使用 useMemo()React 开发者有一些建议

  • 可以依赖 useMemo() 作为性能优化,而不是语义保证
  • 函数内部引用的每个值也应该出现在依赖项数组中

对于下一个示例,将对 <ParentComponent /> 进行一些更改。 下面的代码仅显示对之前创建的 <ParentComponent /> 的新更改。

// components/parent-component.js

import { useState, useEffect, useRef, useMemo } from "react";
import UseMemoCounts from "./use-memo-counts";

export default function ParentComponent() {
  const [times, setTimes] = useState(0);
  const useMemoRef = useRef(0);

  const incrementUseMemoRef = () => useMemoRef.current++;

  // uncomment the next line to test that <UseMemoCounts /> will re-render every t ime the parent re-renders.
  // const memoizedValue = useMemoRef.current++;

// the next line ensures that <UseMemoCounts /> only renders when the times value changes
const memoizedValue = useMemo(() => incrementUseMemoRef(), [times]);

  return (
    <div className="flex flex-col justify-center items-center border-2 rounded-md mt-5 dark:border-yellow-200 max-w-lg m-auto pb-10 bg-gray-900">
        <div className="mt-4 text-center">
          <button
            className="bg-indigo-200 py-2 px-10 rounded-md"
            onClick={() => setTimes(times+1)}
          >
            Force render
          </button>

          <UseMemoCounts memoizedValue={memoizedValue} />
        </div>
    </div>
  );
}

首先,引入了非常重要的 useMemo() Hook。 还引入了 useRef() Hook 来帮助我们跟踪组件中发生了多少次重新渲染。 接下来,声明一个 times 状态,稍后将更新该状态来触发/强制重新渲染。

之后,声明一个 memoizedValue 变量,用于存储 useMemo() Hook 的返回值。 

useMemo() Hook 调用 incrementUseMemoRef 函数,它会在每次依赖项发生变化时将 useMemoRef.current 值加一,即 times 值发生变化。

然后创建一个按钮来点击更新times的值。 单击此按钮将触发我们的 useMemo() Hook,更新 memoizedValue 的值,并重新渲染 <UseMemoCounts /> 组件。

在这个例子中,还将 <Counts /> 组件重命名为 <UseMemoCounts />,它现在需要一个 memoizedValue 属性。

这是它的样子:

// components/use-memo-counts.js

function UseMemoCounts({memoizedValue}) {
  return (
    <div className="mt-3">
      <p className="dark:text-white max-w-md">
        I'll only re-render when you click <span className="font-bold text-indigo-400">Force render.</span> 
        </p>
      <p className="dark:text-white">I've now rendered: <span className="text-green-400">{memoizedValue} time(s)</span> </p>
    </div>
  );
}
export default UseMemoCounts;

现在,当单击任何奶酪按钮时, memoizedValue 不会更新。 但是当单击 Force render 按钮时,看到 memoizedValue 更新并且 <UseMemoCounts /> 组件重新渲染。

如果您注释掉当前的 memoizedValue 行,并取消注释掉它上面的行:

const memoizedValue = useMemoRef.current++;

您将看到 <UseMemoCounts /> 组件在每次 <ParentComponent /> 渲染时重新渲染。

总结:React.memo() 和 useMemo() 的主要区别

从上面的例子中,可以看到 React.memo() 和 useMemo() 之间的主要区别:

  • React.memo() 是一个高阶组件,可以使用它来包装不想重新渲染的组件,除非其中的 props 发生变化
  • useMemo() 是一个 React Hook,可以使用它在组件中包装函数。 可以使用它来确保该函数中的值仅在其依赖项之一发生变化时才重新计算

虽然 memoization 似乎是一个可以随处使用的巧妙小技巧,但只有在绝对需要这些性能提升时才应该使用它。 Memoization 会占用运行它的机器上的内存空间,因此可能会导致意想不到的效果

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
当你在使用React时,你可能会经常遇到需要优化组件性能的情况。React.memouseMemo都是React提供的两种优化性能的方式。 React.memo是一个高阶组件(Higher Order Component),它可以优化组件的性能,使得组件只有在它的props发生改变时才会重新渲染。这种优化是通过比较前一次和当前props的浅层比较来实现的。使用React.memo时,需要将组件作为React.memo函数的参数传递,例如: ``` import React from 'react'; const MyComponent = React.memo(props => { // 组件代码 }); export default MyComponent; ``` useMemo是一个React的hook函数,它可以缓存组件中的一些计算结果,以避免重复计算。useMemo接收两个参数:一个计算函数和一个依赖数组。当依赖数组中的任何一个值发生改变时,useMemo会重新计算并返回新的值。如果依赖数组中的任何一个值都没有发生改变,则会返回上一次缓存的值。使用useMemo时,需要将计算函数作为useMemo的第一个参数传递,依赖数组作为第二个参数传递,例如: ``` import React, { useMemo } from 'react'; const MyComponent = props => { const expensiveCalculation = useMemo(() => { // 计算代码 }, [props.dependency]); // 组件代码 }; export default MyComponent; ``` 需要注意的是,React.memouseMemo都只是对组件性能进行优化的工具,并不是适用于所有情况的万能解决方案。在使用它们时,需要谨慎考虑依赖项和是否真的需要进行性能优化。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

北海屿鹿

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

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

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

打赏作者

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

抵扣说明:

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

余额充值