ReactJS - 使用 useMemo



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

  • 改进复杂计算的算法,使其快速。
  • 仅在绝对必要时才调用复杂计算。

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

使用签名Memo hook

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])

在这里,求和逻辑将在开始时执行一次,并在限制值更改/更新时执行一次。在这两者之间,memoized 值由 useMemo 钩子返回。

应用备忘录钩子

让我们通过在 react 应用程序中应用 useMemo 钩子来深入学习该钩子。

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

create-react-app myapp
cd myapp
npm start

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


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 在文档中显示 sum 和 currentTime 值
  • 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 设置为依赖项。这将确保在更改限值之前不会执行求和逻辑。

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


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 钩子来管理限制变量。useState 钩子将返回两个变量;状态变量 limit 和函数变量 setLimit 用于更新限制变量。
  • 添加了一个新的输入元素,允许用户为限制变量输入新值
  • 添加了一个事件 onChange,以在用户更新限制变量时更新该变量。

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

  • onChange 事件将被触发。
  • setLimit 函数将由 onChange 事件调用。
  • limit 变量将由 setLimit 函数更新。
  • Sum 组件将被重新渲染为状态变量,limit 将更新。
  • useMemo 将在限制变量更新时重新运行逻辑,并将记住新值并将其设置为求和变量。
  • render 函数将使用 sum 变量的新值并渲染它。

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

Memo Hook

保留引用

在本章中,让我们了解如何使用 useMemo 钩子和引用数据类型。一般来说,变量的数据类型分为类别、值类型和引用类型。值类型在堆栈中进行管理,它们作为值在函数之间传递。但是,引用数据类型在堆中进行管理,并使用其内存地址或仅引用在函数之间传递。

值类型如下:

  • Number
  • String
  • Boolean
  • null
  • undefined

参考数据类型如下:

  • Array
  • Objects
  • Functions

一般来说,具有参考数据类型的变量不容易进行比较,即使两个变量的值相同。例如,让我们考虑两个具有值类型的变量和另外两个具有参考数据类型的变量,并进行比较,如下所示 -


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

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


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 propms 来计算值范围的总和。Range 是一个包含两个数字的数组,即范围的开始和结束。
  • 通过 JSX 在文档中显示开始、结束和总值
  • 使用 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 hook 的优点如下 -

  • 简单易用的 API
  • 通俗易懂
  • 提高应用程序的性能

缺点

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

  • 降低应用程序的可读性
  • 降低应用程序的可理解性
  • 调试应用程序很复杂
  • 开发人员应该对 JavaScript 语言有深入的理解才能使用它

总结

通常,React 在内部优化应用程序并提供高性能。尽管如此,在某些情况下,我们可能需要进行干预并提供我们自己的优化。useMemo 在这种情况下帮助我们,并提供了一个简单的解决方案来提高 react 应用程序的性能。总而言之,在绝对必要时使用 useMemo 钩子。否则,让优化和提供高性能来做出反应。