<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          一篇文章帶你理解 React 中最“臭名昭著”的 useMemo 和 useCallback

          共 25060字,需瀏覽 51分鐘

           ·

          2023-03-11 10:49

          大廠技術(shù)  高級前端  Node進階

          點擊上方 程序員成長指北,關(guān)注公眾號

          回復(fù)1,加入高級Node交流群

          原文鏈接: Understanding useMemo and useCallback[1]翻譯原文: https://juejin.cn/post/7165338403465068552譯者: oil歐喲

          前言

          作為一個 React 開發(fā)者,如果你一直覺得 useMemouseCallback 這兩個 Hook 比較難以理解,那么別害怕,事實上很多人都如此。我和其他公司很多的 React 開發(fā)者交流過,大多數(shù)對這兩個 Hook 都是一知半解的狀態(tài)。

          這篇文章就為你答疑解惑,為大家介紹這兩個 Hook 的具體作用,它們的實現(xiàn)原理以及在實際開發(fā)中如何應(yīng)用。

          這篇文章更適合初/中級 React 開發(fā)者用于加深對 React 的理解,如果你才剛剛開始學(xué)習(xí) React ,那么你也可以先將這篇文章收藏起來,在你對 React 有了一定使用經(jīng)驗后再回來學(xué)習(xí)。

          基礎(chǔ)概念

          我們先從 useMemo 開始介紹,useMemo 的基本概念就是:它能幫助我們 “記錄” 每次渲染之間的計算值。這句話可能有些抽象,想要理解它需要你對 React 復(fù)雜的工作原理有一定的心智模型。所以我們先講講 React 的基本工作原理。

          React 所做的主要事情是讓我們的 UI 與我們的 狀態(tài) 保持同步,而要實現(xiàn)它們的同步,就需要執(zhí)行一個叫做 “re-render” (重新渲染) 的操作。

          每一次 重新渲染 都是一次快照,它基于當前應(yīng)用程序的狀態(tài)告訴了應(yīng)用程序的 UI 在某一特定時刻應(yīng)該是什什么樣的。我們可以把它想象成一疊照片,每張照片都記錄了在每個狀態(tài)變量的特定值下事物的樣子。

          舉個例子,我們先定義一個狀態(tài) a ,它的初始值是 hello,我們先把它渲染到頁面上,這時候我們的 UI 上就會有一行 hello

          const [a, setA] = useState("hello")

          return (<span>{a}</span>)

          如果我們將 a 設(shè)置為 world,

          setA("world")

          此時頁面上還是 ”hello“,為了保持狀態(tài)和 UI 同步,就需要觸發(fā)一次 重新渲染 ,這樣 UI 上也變?yōu)榱?“hello”,當然重新渲染不需要我們自己執(zhí)行 ,你在使用 setA 時 React 就會幫我們處理。

          每一次 重新渲染 都會根據(jù)當前的狀態(tài)產(chǎn)生一個 DOM 應(yīng)該是什么樣子的心理圖景。在上面的例子中,我們的狀態(tài)被描繪成 HTML,但本質(zhì)上它是一堆 JS 對象。如果了解過的話就知道它也被稱為 虛擬DOM。

          我們并不需要告訴 React 有哪些 DOM 節(jié)點需要改變。相反,我們告訴 React 的是基于當前狀態(tài)渲染的 UI 應(yīng)該是什么樣的。通過重新渲染,React 創(chuàng)建了一個新的快照,它可以通過比較快照找出需要改變的地方,就像玩一個 "找不同 "的游戲。

          React 在你開箱使用時就進行了大量的優(yōu)化,所以一般來說,重新渲染并不是啥大問題。但是,在某些情況下,這些快照確實需要一段時間來創(chuàng)建。這可能會導(dǎo)致性能問題,比如當用戶執(zhí)行某些操作后,UI 卻不能夠快速的同步修改。

          所以從本質(zhì)上,useMemouseCallback 都是用來幫助我們優(yōu)化 重新渲染 的工具 Hook。它們通過以下兩種方式實現(xiàn)優(yōu)化的效果。

          • 減少在一次渲染中需要完成的工作量。
          • 減少一個組件需要重新渲染的次數(shù)。

          下面我們通過一些實際場景介紹一下這兩個 API。

          1. 需要進行大量計算的場景

          假設(shè)我們寫一個工具來幫助用戶找到 0 和一個用戶傳入的數(shù)字參數(shù) selectedNum 之間的所有質(zhì)數(shù)

          質(zhì)數(shù)就是一個只能被 1 和它自己整除的數(shù)字,比如17。

          下面是實現(xiàn)的代碼:

          import React from 'react';

          function App({

            const [selectedNum, setSelectedNum] = React.useState(100);
            
            // We calculate all of the prime numbers between 0 and the
            // user's chosen number, `selectedNum`:
            const allPrimes = [];
            for (let counter = 2; counter < selectedNum; counter++) {
              if (isPrime(counter)) {
                allPrimes.push(counter);
              }
            }
            
            return (
              <>
                <form>
                  <label htmlFor="num">Your number:</label>
                  <input
                    type="number"
                    value={selectedNum}
                    onChange={(event) =>
           {
                      // 為了防止電腦燒起來,我們限制一下傳入的值最大為 100k
                      let num = Math.min(100_000, Number(event.target.value));
                      
                      setSelectedNum(num);
                    }}
                  />
                </form>
                <p>
                  There are {allPrimes.length} prime(s) between 1 and {selectedNum}:
                  {' '}
                  <span className="prime-list">
                    {allPrimes.join(', ')}
                  </span>
                </p>
              </>

            );
          }

          // isPrime 用于計算傳入的參數(shù)是否為質(zhì)數(shù)
          function isPrime(n){
            const max = Math.ceil(Math.sqrt(n));
            
            if (n === 2) {
              return true;
            }
            
            for (let counter = 2; counter <= max; counter++) {
              if (n % counter === 0) {
                return false;
              }
            }

            return true;
          }

          export default App;

          你不需要看懂上面的每一行代碼,這里分析一下以上代碼的重點:

          • 我們維護了一個狀態(tài) selectedNum
          • 我們使用一個 for 循環(huán)手動計算 0 和 selectedNum 之間的所有質(zhì)數(shù)
          • 我們渲染了一個輸入框,用戶通過輸入改變 selectedNum 的值
          • 我們在頁面中向用戶展示了所有計算出來的質(zhì)數(shù)。

          以上這段代碼執(zhí)行時需要進行大量的計算。如果用戶選擇了一個值很大的 selectedNum,我們將需要遍歷數(shù)以萬計的數(shù)字去判斷每一個是否為質(zhì)數(shù)。而且即使有比我上面使用的算法更有效的素數(shù)判斷算法,但肯定也是需要進行大量計算的。

          在實際開發(fā)中我們很有可能遇到類似的場景。但是有時候我們并不需要重新計算,但仍然執(zhí)行了計算操作,就有會遇到一些性能問題。比如下面這種情況:

          import React from 'react';
          import format from 'date-fns/format';

          function App() {
          const [selectedNum, setSelectedNum] = React.useState(100);

          // `time` 是一個狀態(tài)變量,每秒鐘變化一次,所以它總是與當前時間同步
          const time = useTime();

          const allPrimes = [];
          for (let counter = 2; counter < selectedNum; counter++) {
          if (isPrime(counter)) {
          allPrimes.push(counter);
          }
          }

          return (
          <>
          <p className="clock">
          {format(time, 'hh:mm:ss a')}
          </p>
          <form>
          <label htmlFor="num">Your number:</label>
          <input
          type="number"
          value={selectedNum}
          onChange={(event) => {
          // 為了防止電腦燒起來,我們限制一下傳入的值最大為 100k
          let num = Math.min(100_000, Number(event.target.value));

          setSelectedNum(num);
          }}
          />
          </form>
          <p>
          There are {allPrimes.length} prime(s) between 1 and {selectedNum}:
          {' '}
          <span className="prime-list">
          {allPrimes.join(', ')}
          </span>
          </p>
          </>
          );
          }

          function useTime() {
          const [time, setTime] = React.useState(new Date());

          React.useEffect(() => {
          const intervalId = window.setInterval(() => {
          setTime(new Date());
          }, 1000);

          return () => {
          window.clearInterval(intervalId);
          }
          }, []);

          return time;
          }

          // isPrime 用于計算傳入的參數(shù)是否為質(zhì)數(shù)
          function isPrime(n){
          const max = Math.ceil(Math.sqrt(n));

          if (n === 2) {
          return true;
          }

          for (let counter = 2; counter <= max; counter++) {
          if (n % counter === 0) {
          return false;
          }
          }

          return true;
          }

          export default App;

          現(xiàn)在代碼里定義了兩個狀態(tài):selectedNumtime。time 每秒鐘改變一次,并且在頁面的右上角渲染出來。

          這時我們會發(fā)現(xiàn)一個問題:即便我們沒有改變 selectedNum ,但是由于 time 的改變會引起重新渲染,而重新渲染又會導(dǎo)致質(zhì)數(shù)的大量計算,這樣就浪費了很多性能。

          Javascript 運行時是單線程的,如果我們反復(fù)執(zhí)行這段代碼,就會一直有一個計算任務(wù)占用著線程。這會導(dǎo)致我們其他任務(wù)沒法快速執(zhí)行,整個應(yīng)用會讓人感覺很遲鈍,尤其是在低性能的設(shè)備上感知更加明顯。

          那么我們該如何 繞過 這個計算的事件呢,如果我們已經(jīng)有了某個數(shù)字的質(zhì)數(shù)列表,為什么不重復(fù)使用這個值,而是每次都從頭計算呢?

          這就是 useMemo 能夠幫助我們做到的事情,如下例所示:

          const allPrimes = React.useMemo(() => {
          const result = [];
          for (let counter = 2; counter < selectedNum; counter++) {
          if (isPrime(counter)) {
          result.push(counter);
          }
          }
          return result;
          }, [selectedNum]);

          useMemo 接受兩個參數(shù):

          1. 需要執(zhí)行的一些計算處理工作,包裹在一個函數(shù)中
          2. 一個依賴數(shù)組

          在組件掛載的過程中,當這個組件第一次被渲染時,React 都會調(diào)用這個函數(shù)來執(zhí)行這段計算邏輯,計算所有的質(zhì)數(shù)。無論我們從這個函數(shù)中返回什么值,都會分配給 allPrimes 變量。

          然而,對于每一個后續(xù)的渲染,React 都要從以下兩種情況中做出選擇:

          1. 再次調(diào)用 useMemo 中的計算函數(shù),重新計算數(shù)值
          2. 重復(fù)使用上一次已經(jīng)計算出來的數(shù)據(jù)

          為了做出一個正確的選擇,React 會判斷你傳入的依賴數(shù)組,這個數(shù)組中的每個變量是否在兩次渲染間 值是否改變了 ,如果發(fā)生了改變,就重新執(zhí)行計算的邏輯去獲取一個新的值,否則不重新計算,直接返回上一次計算的值。

          useMemo 本質(zhì)上就像一個小的緩存,而依賴數(shù)組就是緩存的失效策略。

          在上面的例子中,其實本質(zhì)上是在說 “只有當 selectedNum 的值變化時才重新計算質(zhì)數(shù)列表“。 當組件因為其他情況重新渲染,例如狀態(tài) time 的值改變了,useMemo 就會忽略這個計算函數(shù),直接返回之前緩存的值。

          這種緩存的過程通常被稱為 memoization,這就是為什么這個鉤子被稱為 “useMemo”

          另一種解決方法

          useMemo 鉤子確實可以幫助我們避免這里不必要的計算……,但它真的是這里最好的解決方案嗎?

          通常情況下,我們都會通過一些重構(gòu)來避免掉需要使用 useMemo 進行優(yōu)化的場景。如下例:

          //App.js
          import React from 'react';
          import { getHours } from 'date-fns';

          import Clock from './Clock';
          import PrimeCalculator from './PrimeCalculator';

          // 將 PrimeCalculator 轉(zhuǎn)換為純組件
          const PurePrimeCalculator = React.memo(PrimeCalculator);

          function App({
            const time = useTime();

            // 基于當前時間動態(tài)計算一個背景顏色
            const backgroundColor = getBackgroundColorFromTime(time);

            return (
              <div style={{ backgroundColor }}>
                <Clock time={time} />
                <PurePrimeCalculator />
              </div>

            );
          }

          const getBackgroundColorFromTime = (time) => {
            const hours = getHours(time);
            
            if (hours < 12) {
              // A light yellow for mornings
              return 'hsl(50deg 100% 90%)';
            } else if (hours < 18) {
              // Dull blue in the afternoon
              return 'hsl(220deg 60% 92%)'
            } else {
              // Deeper blue at night
              return 'hsl(220deg 100% 80%)';
            }
          }

          function useTime({
            const [time, setTime] = React.useState(new Date());
            
            React.useEffect(() => {
              const intervalId = window.setInterval(() => {
                setTime(new Date());
              }, 1000);
            
              return () => {
                window.clearInterval(intervalId);
              }
            }, []);
            
            return time;
          }

          export default App;
          // PrimeCalculator.js
          import React from 'react';

          function PrimeCalculator() {
          const [selectedNum, setSelectedNum] = React.useState(100);

          const allPrimes = [];
          for (let counter = 2; counter < selectedNum; counter++) {
          if (isPrime(counter)) {
          allPrimes.push(counter);
          }
          }

          return (
          <>
          <form>
          <label htmlFor="num">Your number:</label>
          <input
          type="number"
          value={selectedNum}
          onChange={(event) => {
          // 為了防止電腦燒起來,我們限制一下傳入的值最大為 100k
          let num = Math.min(100_000, Number(event.target.value));

          setSelectedNum(num);
          }}
          />
          </form>
          <p>
          There are {allPrimes.length} prime(s) between 1 and {selectedNum}:
          {' '}
          <span className="prime-list">
          {allPrimes.join(', ')}
          </span>
          </p>
          </>
          );
          }

          function isPrime(n){
          const max = Math.ceil(Math.sqrt(n));

          if (n === 2) {
          return true;
          }

          for (let counter = 2; counter <= max; counter++) {
          if (n % counter === 0) {
          return false;
          }
          }

          return true;
          }

          export default PrimeCalculator;
          // Clock.js
          import React from 'react';
          import format from 'date-fns/format';

          function Clock({
            const time = useTime();
            
            return (
              <p className="clock">
                {format(time, 'hh:mm:ss a')}
              </p>

            );
          }

          import React from 'react';
          import format from 'date-fns/format';

          function Clock({ time }{
            return (
              <p className="clock">
                {format(time, 'hh:mm:ss a')}
              </p>

            );
          }

          export default Clock;

          我將之前的例子抽離為了兩個單獨的組件 ClockPrimeCalculator,從 App 組件抽離出來后,這兩個組件各自維護自己的狀態(tài)數(shù)據(jù),即使其中一個組件重新渲染了也不會影響另外一個。

          這里我們使用 React.memo 包裹著組件保護它不受到無關(guān)狀態(tài)更新的影響。只有在 PurePrimeCalculator 只會在收到新數(shù)據(jù)或內(nèi)部狀態(tài)發(fā)生變化時重新渲染。這種組件被稱為 純組件。本質(zhì)上,我們告訴 React 這個組件在 給定相同輸入的情況下總是會產(chǎn)生相同的輸出 ,并且我們可以跳過沒有 props 和狀態(tài)改變的重渲染。

          在上例中我們將組件引入 App.tsx 后再通過 React.memo 進行包裹,在實際開發(fā)中我們更多的是在組件 export 的時候就使用 React.memo 進行包裹,這樣可以保證組件一直是純組件。上例只是為了更加清楚的在 App.tsx 中展示所有內(nèi)容。

          這里有一個有趣的視角轉(zhuǎn)變: 在前面的例子中,我們是緩存了計算質(zhì)數(shù)的結(jié)果。然而在重構(gòu)后,我們已經(jīng)緩存了了整個組件。但無論使用哪種方式,昂貴的計算操作只有在 selectedNum 的值改變時才會執(zhí)行了,這里兩種方法沒有優(yōu)劣之分,根據(jù)實際情境來使用即可。

          但在實際開發(fā)中你可能會發(fā)現(xiàn) 純組件也經(jīng)常發(fā)生重新渲染,即便它并沒有發(fā)生什么改變。接下來就為大家介紹可以使用 useMemo 來解決的第二種場景。

          2. 引用保留

          在下面的示例中,我們創(chuàng)建了一個 Boxes 組件用于展示幾個不同顏色的容器,純粹是用于裝飾。然后我們還定義了一個跟 Boxes 組件沒啥關(guān)系的 user's name 變量。

          // App.jsx
          import React from 'react';

          import Boxes from './Boxes';

          function App({
            const [name, setName] = React.useState('');
            const [boxWidth, setBoxWidth] = React.useState(1);
            
            const id = React.useId();
            
            // Try changing some of these values!
            const boxes = [
              { flex: boxWidth, background'hsl(345deg 100% 50%)' },
              { flex3background'hsl(260deg 100% 40%)' },
              { flex1background'hsl(50deg 100% 60%)' },
            ];
            
            return (
              <>
                <Boxes boxes={boxes} />
                
                <section>
                  <label htmlFor={`${id}-name`}>
                    Name:
                  </label>
                  <input
                    id={`${id}-name`}
                    type="text"
                    value={name}
                    onChange={(event) =>
           {
                      setName(event.target.value);
                    }}
                  />
                  <label htmlFor={`${id}-box-width`}>
                    First box width:
                  </label>
                  <input
                    id={`${id}-box-width`}
                    type="range"
                    min={1}
                    max={5}
                    step={0.01}
                    value={boxWidth}
                    onChange={(event) =>
           {
                      setBoxWidth(Number(event.target.value));
                    }}
                  />
                </section>
              </>

            );
          }

          export default App;
          //Boxes.jsx
          import React from 'react';

          function Boxes({ boxes }{
            return (
              <div className="boxes-wrapper">
                {boxes.map((boxStyles, index) => (
                  <div
                    key={index}
                    className="box"
                    style={boxStyles}
                  />

                ))}
              </div>

            );
          }

          export default React.memo(Boxes);

          效果如下圖:

          image.png

          我們使用了 React.memo 包裹著 Boxes 組件,使它成為一個純組件,這說明只有在 props 更改時它才會重新渲染

          然而實際使用時你會發(fā)現(xiàn),當用戶輸入 Name 時,Boxes 也會重新渲染。這時候你可能會好奇,有沒有搞錯?!為什么我們的 React.memo() 沒有在這里保護我們的組件?

          Boxes 組件只有 1 個 prop boxes,看似我們在每次渲染時都為其提供了完全相同的數(shù)據(jù)。它每次渲染也總是一樣的:一個紅色的盒子,一個寬紫色的盒子,一個黃色的盒子。我們確實有一個 boxWidth 會影響 boxes 數(shù)組的狀態(tài)變量,但我們沒有改變它!

          問題在于每次 React 重新渲染時,都會重新產(chǎn)生一個 boxes 數(shù)組,這個數(shù)組的值雖然每一次重新渲染都是相同的,但是它的 引用 卻是不同的。

          這里暫時拋開 React 單純討論 JavaScript 可能比較好理解,讓我們看一個類似的例子:

          function getNumbers({
            return [123];
          }

          const firstResult = getNumbers();
          const secondResult = getNumbers();

          console.log(firstResult === secondResult);

          你怎么看?firstResult 等于 secondResult ? 從某種意義上說,它們是相同的。因為兩個變量具有相同的結(jié)構(gòu)[1, 2, 3]。但這不是 === 操作符實際判斷的標準。相反,=== 判斷的是兩個表達式 是否完全相同

          我們創(chuàng)建了兩個不同的數(shù)組。它們可能包含相同的內(nèi)容,但它們不是同一個數(shù)組,就像 兩個同卵雙胞胎不是同一個人一樣。

          image.png

          每次我們調(diào)用 getNumbers 函數(shù)時都會創(chuàng)建一個全新的數(shù)組,一個保存在計算機內(nèi)存中的獨特數(shù)組。如果我們多次調(diào)用它,我們將在內(nèi)存中存儲該數(shù)組的多個副本。

          請注意,簡單的數(shù)據(jù)類型比如 字符串、數(shù)字和布爾值 可以通過值進行比較。但是當涉及到數(shù)組和對象時,它們只能通過引用進行比較。這部分內(nèi)容大家可以參考其他講引用類型的文章,這里不詳細展開。

          回到 React, 我們的 Boxs React 組件也是一個 JavaScript 函數(shù)。當我們渲染它時,我們調(diào)用以下函數(shù):

          // 每次渲染組件都會調(diào)用 App 函數(shù)
          function App({
            // ...創(chuàng)建一個全新的數(shù)組...
            const boxes = [
              { flex: boxWidth, background'hsl(345deg 100% 50%)' },
              { flex3background'hsl(260deg 100% 40%)' },
              { flex1background'hsl(50deg 100% 60%)' },
            ];
            // ...然后將數(shù)組作為 prop 傳入組件!
            return (
              <Boxes boxes={boxes} />
            );
          }

          name 狀態(tài)更改時,我們的 App 組件將重新渲染,該組件將重新運行所有代碼,并構(gòu)建一個全新的 boxes 數(shù)組,并將其傳遞到 Boxes 組件。此時 Boxes 組件重新渲染,因為我們給了它一個全新的數(shù)組!

          boxes 數(shù)組的結(jié)構(gòu)在不同的渲染之間雖然沒有變化,但是這不相關(guān)。React 只知道 Boxes 組件 prop 收到了一個新創(chuàng)建的,從未見過的數(shù)組。

          為了解決這個問題,我們可以使用 useMemo hook:

          const boxes = React.useMemo(() => {
            return [
              { flex: boxWidth, background'hsl(345deg 100% 50%)' },
              { flex3background'hsl(260deg 100% 40%)' },
              { flex1background'hsl(50deg 100% 60%)' },
            ];
          }, [boxWidth]);

          這里不像我們之前的例子,相比于質(zhì)數(shù),這里我們不需要擔心計算的代價。我們的唯一目標是保留對特定數(shù)組的引用。我們將 boxWidth 列為一個依賴項,因為我們確實希望在用戶調(diào)整紅色框的寬度時重新渲染 Box 組件。

          這里有一個圖可以幫助你理解。在此之前,我們創(chuàng)建了一個全新的數(shù)組,作為每張快照的一部分:

          image.png

          然而通過 useMemo 我們復(fù)用了一個之前創(chuàng)建的 boxes 數(shù)組。

          image.png

          通過在多次渲染中保留相同的引用,我們允許純組件以我們想要的方式運作,忽略掉那些不影響用戶界面的渲染。

          useCallback hook

          好不容易介紹完了 useMemo,那么 useCallback 呢?

          簡單概括:useMemouseCallback 是一個東西,只是將返回值從 數(shù)組/對象 替換為了 函數(shù)。

          函數(shù)是與數(shù)組和對象類似,都是通過引用而不是通過值進行比較的:

          const functionOne = function({
            return 5;
          };
          const functionTwo = function({
            return 5;
          };

          console.log(functionOne === functionTwo); // false

          這意味著如果我們在組件中定義一個函數(shù),它將在每個渲染中重新生成,每次生成一個相同但是唯一的函數(shù)。

          讓我們看一個例子:

          //App.jsx
          import React from 'react';

          import MegaBoost from './MegaBoost';

          function App({
            const [count, setCount] = React.useState(0);

            function handleMegaBoost({
              setCount((currentValue) => currentValue + 1234);
            }

            return (
              <>
                Count: {count}
                <button
                  onClick={() =>
           {
                    setCount(count + 1)
                  }}
                >
                  Click me!
                </button>
                <MegaBoost handleClick={handleMegaBoost} />
              </>

            );
          }

          export default App;
          // MegaBoost.jsx
          import React from 'react';

          function MegaBoost({ handleClick }{
            console.log('Render MegaBoost');
            
            return (
              <button
                className="mega-boost-button"
                onClick={handleClick}
              >

                MEGA BOOST!
              </button>

            );
          }

          export default React.memo(MegaBoost);

          效果如圖:

          image.png

          這段代碼寫了一個經(jīng)典的計數(shù)器 app,但是帶有一個特殊的 “Mega Boost” 按鈕。點擊按鈕會大量增加計數(shù),以防您趕時間并且不想多次單擊標準按鈕。

          由于使用了 React.memo 進行包裹, MegaBoost 組件是純組件,它雖然不依賴于 count ……但它會在更改時重新渲染 count!

          就像我們在前面 boxes 數(shù)組中看到的那樣,這里的問題是我們在每次渲染時都生成了一個全新的函數(shù)。如果我們渲染 3 次,我們將創(chuàng)建 3 個獨立 handleMegaBoost 的函數(shù),突破 React.memo 的保護。

          如果使用我們前面所學(xué)到的 useMemo,我們可以解決這樣的問題:

          const handleMegaBoost = React.useMemo(() => {
            return function({
              setCount((currentValue) => currentValue + 1234);
            }
          }, []);

          這里不是返回一個數(shù)組,而是返回一個 函數(shù)。然后將該函數(shù)存儲在 handleMegaBoost 變量中。

          這種寫法雖然也可以,但是有一種更好的方法:

          const handleMegaBoost = React.useCallback(() => {
            setCount((currentValue) => currentValue + 1234);
          }, []);

          useCallback 的用途與 useMemo 相同,但它是專門為函數(shù)構(gòu)建的。我們直接給返回它一個函數(shù),它會記住這個函數(shù),在渲染之間線程化它。

          換句話說就是以下的兩種實現(xiàn)方式的效果是相同的:

          React.useCallback(function helloWorld(){}, []);

          // ...功能相當于:
          React.useMemo(() => function helloWorld(){}, []);

          useCallback 是一種語法糖,它的存在存粹是為了讓我們在緩存回調(diào)函數(shù)的時候可以方便點。

          當使用這些 Hook 時

          好了,我們已經(jīng)學(xué)習(xí)了 useMemouseCallback 時如何允許我們在多次渲染之間線程化引用,以復(fù)用復(fù)雜的計算或者避免破壞純組件。

          但還有一個問題是: 我們應(yīng)該在什么情況下使用這兩個 Hook ?

          在我個人看來,將每個對象/數(shù)組/函數(shù)包裝在這些 hook 是在浪費時間。在大多數(shù)情況下,這些優(yōu)化的好處幾乎可以忽略不計; 因為 React 內(nèi)部是高度優(yōu)化的,并且 重新渲染通常并不像我們通常認為的那樣慢或昂貴!

          使用這些 hook 的最佳方法是響應(yīng)問題。如果你注意到你的 app 變得有些遲鈍,你可以使用 React Profiler 來尋找慢速渲染。在某些情況下,可以通過重構(gòu) app 來提高性能。在其他情況下,useMemouseCallback 可以幫助加快速度。

          也就是說,在某些情況下,我確實會先發(fā)制人地應(yīng)用這些 hook。

          未來可能會發(fā)生的改變

          React 團隊正在積極研究是否有可能在編譯步驟中 “自動緩存” 代碼。雖然它仍然處于研究階段,但是通過早期的實驗看起來很有希望。

          也許在未來這些優(yōu)化 React 都會為我們提前做好,但在此之前我們還是得自己去做一些優(yōu)化

          要了解更多信息,可以看看黃玄的這個演講 “React without memo”[2]

          通用自定義 hook

          我最喜歡的自定義 hook 之一是 useToggle,這是一個友好的助手,其工作方式幾乎與 useState 完全相同,但只能在 true 和 false 之間切換狀態(tài)變量:

          function App({
            const [isDarkMode, toggleDarkMode] = useToggle(false);
            return (
              <button onClick={toggleDarkMode}>
                Toggle color theme
              </button>

            );
          }

          這里是這個自定義 hook 的代碼實現(xiàn):

          function useToggle(initialValue{
            const [value, setValue] = React.useState(initialValue);
            
            const toggle = React.useCallback(() => {
              setValue(v => !v);
            }, []);
            
            return [value, toggle];
          }

          注意這里的 toggle 函數(shù)使用了 useCallback 進行緩存。

          當咱們構(gòu)建這樣的自定義可復(fù)用 hook 時,我希望使它們盡可能高性能,因為我不知道它們將來在哪里使用。在95% 的情況下,這可能是過度封裝的,但是如果我使用這個 hook 3040 次,這將很有可能有助于提高我們的 app 的性能。

          內(nèi)部 context providers

          當我們通過 context 在組件之間共享數(shù)據(jù)時,通常會傳遞一個大的對象作為 value 屬性。

          一般來說,將這個對象緩存起來是個好方法:

          const AuthContext = React.createContext({});

          function AuthProvider({ user, status, forgotPwLink, children }){
            const memoizedValue = React.useMemo(() => {
              return {
                user,
                status,
                forgotPwLink,
              };
            }, [user, status, forgotPwLink]);
            
            return (
              <AuthContext.Provider value={memoizedValue}>
                {children}
              </AuthContext.Provider>

            );
          }

          這樣寫有什么好處呢?因為可能有幾十個純組件使用這個 context 。如果沒有使用 useMemo,那么當 AuthProvider 的父組件恰好重新渲染時,這些使用 context 組件都將被迫重新渲染。

          React 的樂趣

          恭喜你看到了這里,我知道這里面可能有些內(nèi)容你從未了解過。這兩個 hook 確實是比較棘手,畢竟 React 本身就是龐大且復(fù)雜的,是一個上手難度比較高的工具!

          但事實是: 如果你能克服最初的困難,使用 React 絕對是一種樂趣。

          我從 2015 年開始使用 React,它已經(jīng)成為我最喜歡的構(gòu)建復(fù)雜用戶界面和 Web 應(yīng)用程序的方式。我已經(jīng)嘗試了幾乎所有的 JS 框架,但是我覺得它們的效率都不如 React 的效率。

          如果你覺得這篇博客文章哪怕只有一點點幫助,你都會從中學(xué)到很多東西。

          參考資料

          [1]

          https://www.joshwcomeau.com/react/usememo-and-usecallback/: https://www.joshwcomeau.com/react/usememo-and-usecallback/

          [2]

          https://www.youtube.com/watch?v=lGEMwh32soc: https://www.youtube.com/watch?v=lGEMwh32soc

          Node 社群


          我組建了一個氛圍特別好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你對Node.js學(xué)習(xí)感興趣的話(后續(xù)有計劃也可以),我們可以一起進行Node.js相關(guān)的交流、學(xué)習(xí)、共建。下方加 考拉 好友回復(fù)「Node」即可。

             “分享、點贊在看” 支持一波??

          瀏覽 53
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  男人天堂999 | 色哟哟无码精品一区二区三区 | 青草草视频精品视频免费观看 | 毛片一区二区三区 | 99久久精品国产色欲 |