ReactJS 教程

ReactJS - 主页 ReactJS - 简介 ReactJS - 路线图 ReactJS - 安装 ReactJS - 功能 ReactJS - 优势和缺点 ReactJS - 架构 ReactJS - 创建 React 应用程序 ReactJS - JSX ReactJS - 组件 ReactJS - 嵌套组件 ReactJS - 使用组件 ReactJS - 集合组件 ReactJS - 样式 ReactJS - 属性 (props) ReactJS - 使用属性创建组件 ReactJS - props 验证 ReactJS - 构造函数 ReactJS - 组件生命周期 ReactJS - 事件管理 ReactJS - 创建事件感知组件 ReactJS - Expense Manager 事件 ReactJS - 状态管理 ReactJS - 状态管理 API ReactJS - 无状态组件 ReactJS - Hooks 进行状态管理 ReactJS - Hooks 的组件生命周期 ReactJS - 布局组件 ReactJS - 分页 ReactJS - Material UI ReactJS - Http 客户端编程 ReactJS - 表单编程 ReactJS - 受控组件 ReactJS - 非受控组件 ReactJS - Formik ReactJS - 条件渲染 ReactJS - 列表 ReactJS - Key 键 ReactJS - 路由 ReactJS - Redux ReactJS - 动画 ReactJS - Bootstrap ReactJS - Map ReactJS - 表格 ReactJS - 使用 Flux 管理状态 ReactJS - 测试 ReactJS - CLI 命令 ReactJS - 构建和部署 ReactJS - 示例

Hooks

ReactJS - Hooks 简介 ReactJS - 使用 useState ReactJS - 使用 useEffect ReactJS - 使用 useContext ReactJS - 使用 useRef ReactJS - 使用 useReducer ReactJS - 使用 useCallback ReactJS - 使用 useMemo ReactJS - 自定义 Hooks

ReactJS 高级

ReactJS - 可访问性 ReactJS - 代码拆分 ReactJS - 上下文 ReactJS - 错误边界 ReactJS - 转发 Refs ReactJS - 片段 ReactJS - 高阶组件 ReactJS - 与其他库集成 ReactJS - 优化性能 ReactJS - Profiler API ReactJS - Portals ReactJS - 不使用 ES6 ECMAScript ReactJS - 不使用 JSX 的 React ReactJS - Reconciliation ReactJS - Refs 和 DOM ReactJS - 渲染道具 ReactJS - 静态类型检查 ReactJS - 严格模式 ReactJS - Web 组件

其他概念

ReactJS - 日期选择器 ReactJS - Helmet ReactJS - 内联样式 ReactJS - PropTypes ReactJS - BrowserRouter ReactJS - DOM ReactJS - 轮播 ReactJS - 图标 ReactJS - 表单组件 ReactJS - 参考 API

ReactJS 有用资源

ReactJS - 快速指南 ReactJS - 备忘录 Axios - 备忘录 ReactJS - 有用资源 ReactJS - 讨论


Hooks:useMemo

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

  • 改进复杂计算的算法并使其快速运行。

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

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

useMemo Hooks(钩子)的签名

useMemo Hooks(钩子)的签名如下 −

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

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

输入参数如下 −

  • expensive_fn −执行昂贵的逻辑并返回要记忆的输出。

  • dependency_array − 保存昂贵的逻辑所依赖的变量。如果数组的值发生变化,则必须重新运行昂贵的逻辑并更新值。

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

useMemo Hooks(钩子)的用法如下 −

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

此处,求和逻辑将在开始时以及限制值更改/更新时执行一次。在此期间,useMemo Hooks(钩子)返回记忆值。

应用 memo Hooks(钩子)

让我们通过在 React 应用程序中应用Hooks(钩子)来深入学习 useMemo Hooks(钩子)。

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

create-react-app myapp
cd myapp
npm start

接下来,在 components 文件夹 (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 Hooks(钩子)设置状态中的当前时间。它返回两个变量;一个状态变量,currentTime 和一个函数 setCurrentTime 来更新状态变量 currentTime。

  • 使用 useEffect Hooks(钩子)通过 setInterval 函数以 1 秒为间隔定期更新时间。我们定期更新时间,以确保组件定期重新渲染。

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

  • 通过 JSX 在文档中显示 sumcurrentTime

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

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

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 Hooks(钩子)。这将防止每次重新渲染时重新计算求和逻辑。

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 Hooks(钩子)来管理限制变量。useState Hooks(钩子)将返回两个变量;一个状态变量 limit 和一个函数变量 setLimit,用于更新 limit 变量。

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

  • 添加了一个事件 onChange,用于在用户更新 limit 变量时对其进行更新。

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

  • onChange 事件将被触发。

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

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

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

  • useMemo 将在限制变量更新时重新运行逻辑,它将记住新值并将其设置为 sum 变量。

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

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

Memo Hook

保留引用

让我们在本章中了解如何使用 useMemo Hooks(钩子)以及引用数据类型。一般来说,变量的数据类型分为类别、值类型和引用类型。值类型在堆栈中管理,并以值的形式在函数之间传递。但是,引用数据类型在堆中管理,并使用其内存地址或简单的引用在函数之间传递。

值类型如下 −

  • Number

  • String

  • Boolean

  • 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 文件夹 (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 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 Hooks(钩子)的优点如下 −

  • 简单易用的 API

  • 易于理解

  • 提高应用程序的性能

缺点

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

  • 降低应用程序的可读性

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

  • 应用程序调试很复杂

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

总结

一般来说,React 会在内部优化应用程序并提供高性能。不过,在某些情况下,我们可能需要干预并提供自己的优化。useMemo 可以帮助我们解决这种情况,并提供一种简单的解决方案来提高 React 应用程序的性能。总而言之,在绝对必要时使用 useMemo Hooks(钩子)。否则,让 React 自己来优化和提供高性能。