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

本文涉及的产品
文档翻译,文档翻译 1千页
图片翻译,图片翻译 100张
语种识别,语种识别 100万字符
简介: 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 会占用运行它的机器上的内存空间,因此可能会导致意想不到的效果。


相关文章
|
1月前
|
JavaScript 前端开发 开发者
React和Vue有什么区别?
React 和 Vue 都有各自的优势和特点,开发者可以根据项目的需求、团队的技术背景以及个人的喜好来选择使用。无论是 React 还是 Vue,它们都在不断发展和完善,为前端开发提供了强大的支持。
88 2
|
1月前
|
JavaScript 前端开发 算法
React 框架和 Vue 框架的区别是什么?
React框架和Vue框架都是目前非常流行的前端JavaScript框架,它们在很多方面存在区别
|
1月前
|
JavaScript 前端开发 算法
在性能上,React和Vue有什么区别
【10月更文挑战第23天】在性能上,React和Vue有什么区别
21 1
|
2月前
|
前端开发 JavaScript UED
react-router 里的 Link 标签和 a 标签有什么区别
`react-router` 中的 `Link` 标签与 HTML 中的 `a` 标签的主要区别在于:`Link` 是专门为 React 应用设计的,用于实现客户端路由导航,不会触发页面的重新加载,而 `a` 标签则会刷新整个页面。使用 `Link` 可以提升应用的性能和用户体验。
|
2月前
|
前端开发 开发者 UED
React 18 与之前版本的主要区别
【10月更文挑战第12天】 总的来说,React 18 的这些区别体现了 React 团队对于提升应用性能、用户体验和开发效率的持续努力。开发者需要适应这些变化,充分利用新特性来构建更出色的应用。同时,随着技术的不断发展,React 也将继续演进,为开发者带来更多的创新和便利。
|
1月前
|
开发框架 JavaScript 前端开发
React和Vue之间的区别是什么
【10月更文挑战第23天】React和Vue之间的区别是什么
18 0
|
1月前
|
JavaScript 前端开发 Android开发
React和Vue之间的区别是什么
【10月更文挑战第23天】React和Vue之间的区别是什么
31 0
|
2月前
|
存储 前端开发 JavaScript
React useState 和 useRef 的区别
本文介绍了 React 中 `useState` 和 `useRef` 这两个重要 Hook 的区别和使用场景。`useState` 用于管理状态并在状态变化时重新渲染组件,适用于表单输入、显示/隐藏组件、动态样式等场景。`useRef` 则用于在渲染之间保持可变值而不触发重新渲染,适用于访问 DOM 元素、存储定时器 ID 等场景。文章还提供了具体的代码示例,帮助读者更好地理解和应用这两个 Hook。
61 0
|
3月前
|
缓存 前端开发
React中函数式Hooks之memo、useCallback的使用以及useMemo、useCallback的区别
React中的`memo`是高阶组件,类似于类组件的`PureComponent`,用于避免不必要的渲染。`useCallback` Hook 用于缓存函数,避免在每次渲染时都创建新的函数实例。`memo`可以接收一个比较函数作为第二个参数,以确定是否需要重新渲染组件。`useMemo`用于缓存计算结果,避免重复计算。两者都可以用来优化性能,但适用场景不同:`memo`用于组件,`useMemo`和`useCallback`用于值和函数的缓存。
106 1
|
3月前
|
缓存 前端开发
React中函数式Hooks之useMemo的使用
React的`useMemo` Hook 用于优化性能,通过记忆返回值避免重复计算。它接收一个函数和一个依赖数组,只有当依赖项改变时,才会重新计算被记忆的值。这可以用于避免在每次渲染时都进行昂贵的计算,或者防止子组件不必要的重新渲染。例如,可以在父组件中使用`useMemo`包裹子组件,以依赖特定的props,从而控制子组件的渲染。
42 0