鉤子: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 本身。

廣告