钩子:useMemo



React 提供了一套核心内置钩子,以简化开发并构建高效的应用程序。应用程序的重要特性之一是**良好的性能**。每个应用程序都将具有一些复杂的计算逻辑,这将降低应用程序的性能。我们有两个选择来应对复杂的计算。

  • 改进复杂计算的算法并使其更快。

  • 仅在绝对必要时调用复杂计算。

useMemo 钩子通过提供一个选项来帮助我们提高应用程序的性能,该选项仅在绝对必要时调用复杂计算。useMemo 保留/记忆化复杂计算的输出并返回它,而不是重新计算复杂计算。useMemo 将知道何时使用记忆化值以及何时重新运行复杂计算。

useMemo 钩子的签名

useMemo 钩子的签名如下所示:

const <output of the expensive logic> = useMemo(<expensive_fn>, <dependency_array>);

在这里,useMemo 接受两个输入并返回一个计算值。

输入参数如下:

  • expensive_fn - 执行昂贵的逻辑并将输出返回以进行记忆化。

  • dependency_array - 保存变量,昂贵的逻辑依赖于这些变量。如果数组的值发生更改,则必须重新运行昂贵的逻辑并更新值。

useMemo 钩子的输出是expensive_fn的输出。useMemo 将通过执行函数或获取记忆化值来返回输出。

useMemo 钩子的用法如下:

const limit = 1000;
const val = useMemo(() => {
   var sum = 0;
   for(let i = 1; i <= limit; i++) {
      sum += i;
   }
   return sum;
}, [limit])

在这里,求和逻辑将在开始时执行一次,以及每当 limit 值更改/更新时。在此期间,记忆化值由 useMemo 钩子返回。

应用记忆化钩子

让我们通过在 React 应用程序中应用钩子来深入了解useMemo 钩子。

首先,使用 create-react-app 命令创建并启动一个 React 应用程序,如下所示:

create-react-app myapp
cd myapp
npm start

接下来,在 components 文件夹下创建一个组件 Sum(src/components/Sum.js)

import React from "react";
function Sum() {
   return <div>Sum</div>
}
export default Sum

接下来,使用我们的 Sum 组件更新根组件,如下所示:

import './App.css';
import Sum from './components/Sum';
function App() {
   return (
      <div>
         <Sum />
      </div>
   );
}
export default App;

接下来,打开Sum.js 并添加一个状态来表示当前时间,并通过 setInterval 更新当前时间,如下所示:

import React, {
   useState,
   useEffect,
   useMemo
} from "react";
function Sum() {
   let [currentTime, setCurrentTime] = useState(new Date())
   useEffect(() => {
      let interval = setInterval(() => setCurrentTime(new Date()), 1000)
      return () => { clearInterval(interval) }
   })
   const limit = 1000;
   var sum = 0;
   for(let i = 1; i <= limit; i++) {
      sum += i;
   }
   return (
      <div style={ {padding: "5px" } }>
         <div>Summation of values from <i>1</i> to <i>{limit}</i>:
            <b>{sum}</b></div>
         <div>Time: <b>{currentTime.toLocaleString().split(',')[1]}</b></div>
      </div>
   )
}
export default Sum

这里我们有:

  • 使用setState 使用 useState 钩子在状态中设置当前时间。它返回两个变量;一个状态变量currentTime 和一个函数setCurrentTime 来更新状态变量 currentTime。

  • 使用useEffect 钩子使用 setInterval 函数以 1 秒的正则间隔更新时间。我们以正则间隔更新时间以确保组件以正则间隔重新渲染。

  • 使用clearInterval 在组件卸载时移除更新时间功能。

  • 通过 JSX 在文档中显示sumcurrentTime

  • Sum 组件每秒重新渲染一次以更新 currentTime。在每次渲染期间,即使没有必要,求和逻辑也会执行。

接下来,让我们引入useMemo 钩子以防止在重新渲染之间重新计算求和逻辑,如下所示:

import React, { useState, useEffect, useMemo } from "react";
function Sum() {
   let [currentTime, setCurrentTime] = useState(new Date())
   useEffect(() => {
      let interval = setInterval(() => setCurrentTime(new Date()), 1000)
      return () => { clearInterval(interval) }
   })
   const limit = 1000;
   const sum = useMemo(() => {
      var sum = 0;
      for(let i = 1; i <= limit; i++) {
         sum += i;
      }
      return sum;
   }, [limit])
   return (
      <div style={ {padding: "5px" } }>
         <div>Summation of values from <i>1</i> to <i>{limit}</i>: <b>{sum}</b></div>
         <div>Time: <b>{currentTime.toLocaleString().split(',')[1]}</b></div>
      </div>
   )
}
export default Sum

这里我们有,将求和逻辑转换为函数并将其传递到useMemo 钩子中。这将防止在每次重新渲染时重新计算求和逻辑。

limit 设置为依赖项。这将确保在limit 值更改之前不会执行求和逻辑。

接下来,添加一个输入以更改限制,如下所示:

import React, {
   useState,
   useEffect,
   useMemo
} from "react";
function Sum() {
   let [currentTime, setCurrentTime] = useState(new Date())
   let [limit, setLimit] = useState(10);
   useEffect(() => {
      let interval = setInterval(() => setCurrentTime(new Date()), 1000)
      return () => { clearInterval(interval) }
   })
   const sum = useMemo(() => {
   var sum = 0;
   for(let i = 1; i <= limit; i++) {
      sum += i;
   }
      return sum;
   }, [limit])
   return (<div style={ {padding: "5px" } }>
   <div><input value={limit} onChange={ (e) => { setLimit(e.target.value) }} /></div>
      <div>Summation of values from <i>1</i> to <i>{limit}</i>: <b>{sum}</b></div>
      <div>Time: <b>{currentTime.toLocaleString().split(',')[1]}</b></div>
   </div>)
}
export default Sum

这里我们有:

  • 使用useState 钩子来管理 limit 变量。useState 钩子将返回两个变量;一个状态变量limit 和一个函数变量setLimit 来更新limit 变量。

  • 添加了一个新的输入元素,允许用户为limit 变量输入新值

  • 添加了一个事件onChange 以在用户更新时更新limit 变量。

当用户在输入元素中输入新值时,

  • onChange 事件将被触发。

  • setLimit 函数将由onChange 事件调用。

  • limit 变量将由setLimit 函数更新。

  • Sum 组件将重新渲染,因为状态变量limit 已更新。

  • useMemo 将重新运行逻辑,因为 limit 变量已更新,它将记忆化新值并将其设置为sum 变量。

  • render 函数将使用sum 变量的新值并渲染它。

最后,打开浏览器并检查应用程序。

Memo Hook

保留引用

让我们在本节中了解如何在useMemo 钩子中结合引用数据类型使用。通常,变量的数据类型属于值类型和引用类型类别。值类型在堆栈中管理,并且它们作为值在函数之间传递。但是,引用数据类型在堆中管理,并使用其内存地址或简单的引用在函数之间传递。

值类型如下:

  • 数字

  • 字符串

  • 布尔值

  • null

  • undefined

引用数据类型如下:

  • 数组

  • 对象

  • 函数

通常,即使两个变量的值相同,具有引用数据类型的变量也不易比较。例如,让我们考虑两个具有值类型的变量和另外两个具有引用数据类型的变量,并像下面这样比较它们:

var a = 10
var b = 10
a == b // true
var x = [10, 20]
var y = [10, 20]
x == y // false

即使 x 和 y 在值方面相同,但两者都指向内存中的不同位置。useMemo 可用于正确记忆化具有引用数据类型的变量。

useMemo(x) == useMemo(y) // true

让我们创建一个新的 React 应用程序以更好地理解useMemo 处理引用数据类型的用法。

首先,使用 create-react-app 命令创建并启动一个 React 应用程序,如下所示:

create-react-app myapp
cd myapp
npm start

接下来,在 components 文件夹下创建一个组件 PureSumComponent(src/components/PureSumComponent.js)

import React from "react";
function PureSumComponent() {
   return <div>Sum</div>
}
export default PureSumComponent

接下来,使用PureSumComponent 组件更新根组件,如下所示:

import './App.css';
import PureSumComponent from './components/PureSumComponent';
function App() {
   return (
      <div style={{ padding: "5px" }}>
         <PureSumComponent />
      </div>
   );
}
export default App;

接下来,打开PureSumComponent.js 并添加一个 props range 来表示数字范围,如下所示:

import React from "react";
function PureSumComponent(props) {
   const range = props['range'];
   const start = range[0];
   const end = range[1];
   console.log('I am inside PureSumComponent component')
   var sum = 0;
   for(let i = start; i <= end; i++) {
      sum += i;
   }
   return (
      <div>
         <div>Summation of values from <i>{start}</i> to
            <i>{end}</i>: <b>{sum}</b></div>
      </div>
   )
}
export default React.memo(PureSumComponent)

这里:

  • 使用range props 计算值的范围的总和。range 是一个包含两个数字的数组,即范围的开始和结束。

  • 通过 JSX 在文档中显示startendsum

  • 使用 React.memo 包装组件以记忆化组件。由于组件将为给定的输入集呈现相同的输出,因此在 React 中称为PureComponent

接下来,让我们更新App.js 并使用PureSumComponent,如下所示:

import React, {useState, useEffect} from 'react';
import PureSumComponent from './components/PureSumComponent';
function App() {
   var input = [1,1000]
   const [currentTime, setCurrentTime] = useState(new Date())
   useEffect(() => {
      let interval = setInterval(() => {
         setCurrentTime(new Date())
      }, 1000)
      return () => clearInterval(interval)
   }, [currentTime])
   return (
      <div style={ {padding: "5px" } }>
         <PureSumComponent range={input}/>
         <div>Time: <b>{currentTime.toLocaleString().split(',')[1]}</b></div>
      </div>
   );
}
export default App;

这里,我们包含了一个状态currentTime 并使用setInterval 每秒更新它,以确保组件每秒重新渲染。

我们可能认为PureSumComponent 不会每秒重新渲染一次,因为它使用了React.memo。但是,它将重新渲染,因为 props 是一个数组,并且它将每秒创建一个新的引用。您可以通过检查控制台来确认这一点。

接下来,更新根组件并使用 useMemo 保留范围数组,如下所示:

import React, {useState, useEffect, useMemo} from 'react';
import PureSumComponent from './components/PureSumComponent';
function App() {
   const input = useMemo(() => [1, 1000], [])
   const [currentTime, setCurrentTime] = useState(new Date())
   useEffect(() => {
      let interval = setInterval(() => {
         setCurrentTime(new Date())
      }, 1000)
      return () => clearInterval(interval)
   }, [currentTime])
   return (
      <div style={ {padding: "5px" } }>
         <PureSumComponent range={input}/>
         <div>Time: <b>{currentTime.toLocaleString().split(',')[1]}</b></div>
      </div>
   );
}
export default App;

在这里,我们使用了useMemo来保留范围数组。

最后,在浏览器中检查应用程序,它将不会每秒重新渲染PureSumComponent

优点

useMemo钩子的优点如下:

  • 简单的 API

  • 易于理解

  • 提高应用程序性能

缺点

从技术上讲,useMemo没有缺点。但是,在应用程序中大量使用useMemo会导致以下缺点。

  • 降低应用程序的可读性

  • 降低应用程序的可理解性

  • 应用程序调试复杂

  • 开发人员应该对 JavaScript 语言有深入的了解才能使用它

总结

一般来说,React 内部会优化应用程序并提供高性能。但是,我们可能需要介入并在某些场景中提供我们自己的优化。useMemo 在这种情况下帮助我们提供了一个简单的解决方案来提高 React 应用程序的性能。总而言之,在绝对必要时使用useMemo钩子。否则,将优化和提供高性能的任务留给 React 本身。

广告