如何利用Memoization提高React性能

本篇內(nèi)容介紹了“如何利用Memoization提高React性能”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠?qū)W有所成!

創(chuàng)新互聯(lián)是一家專業(yè)提供鳳縣企業(yè)網(wǎng)站建設,專注與成都網(wǎng)站制作、網(wǎng)站建設、H5開發(fā)、小程序制作等業(yè)務。10年已為鳳縣眾多企業(yè)、政府機構(gòu)等服務。創(chuàng)新互聯(lián)專業(yè)網(wǎng)站設計公司優(yōu)惠進行中。

如何利用Memoization提高React性能

React 是如何渲染視圖的?

在討論 React 中的 Memoization 細節(jié)之前,讓我們先來看看 React 是如何使用虛擬 DOM 渲染 UI 的?!鞠嚓P推薦:redis視頻教程】

常規(guī) DOM 基本上包含一組用樹的形式保存的節(jié)點。DOM 中的每個節(jié)點代表一個 UI 元素。每當應用程序中出現(xiàn)狀態(tài)變更時,該 UI 元素及其所有子元素的相應節(jié)點都會在 DOM 樹中更新,然后會觸發(fā) UI 重繪。

在高效的 DOM 樹算法的幫助下,更新節(jié)點的速度更快,但重繪的速度很慢,并且當該 DOM 具有大量 UI 元素時,可能會影響性能。因此,在 React 中引入了虛擬 DOM。

這是真實 DOM 的虛擬表示?,F(xiàn)在,每當應用程序的狀態(tài)有任何變化時,React 不會直接更新真正的 DOM,而是創(chuàng)建一個新的虛擬 DOM。然后 React 會將此新的虛擬 DOM 與之前創(chuàng)建的虛擬 DOM 進行比較,找到有差異的地方(譯者注:也就是找到需要被更新節(jié)點),然后進行重繪。

根據(jù)這些差異,虛擬 DOM 能更高效地更新真正的 DOM。這樣提高了性能,因為虛擬 DOM 不會簡單地更新 UI 元素及其所有子元素,而是有效地僅更新實際 DOM 中必要且最小的更改。

為什么需要 Memoization?

在上一節(jié)中,我們看到了 React 如何使用虛擬 DOM 有效地執(zhí)行 DOM 更新操作來提高性能。在本節(jié)中,我們將介紹一個例子,該例子解釋了為了進一步提高性能而需要使用 Memoization。

我們將創(chuàng)建一個父類,包含一個按鈕,用于遞增名為 count 的變量。父組件還調(diào)用了子組件,并向其傳遞參數(shù)。我們還在 render 方法中添加了 console.log() 語句:

//Parent.js
class Parent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }

  handleClick = () => {
    this.setState((prevState) => {
      return { count: prevState.count + 1 };
    });
  };

  render() {
    console.log("Parent render");
    return (
      <div className="App">
        <button onClick={this.handleClick}>Increment</button>
        <h3>{this.state.count}</h3>
        <Child name={"joe"} />
      </div>
    );
  }
}

export default Parent;

此示例的完整代碼可在 CodeSandbox 上查看。

我們將創(chuàng)建一個 Child 類,該類接受父組件傳遞的參數(shù)并將其顯示在 UI 中:

//Child.js
class Child extends React.Component {
  render() {
    console.log("Child render");
    return (
      <div>
        <h3>{this.props.name}</h3>
      </div>
    );
  }
}

export default Child;

每當我們點擊父組件中的按鈕時,count 值都會更改。由于 state 變化了,因此父組件的 render 方法被執(zhí)行了。

傳遞給子組件的參數(shù)在每次父組件重新渲染時都沒有改變,因此子組件不應重新渲染。然而,當我們運行上面的代碼并繼續(xù)遞增 count 時,我們得到了以下輸出:

Parent render
Child render
Parent render
Child render
Parent render
Child render

你可以在這個 sandbox 中體驗上述示例,并查看控制臺的輸出結(jié)果。

從輸出中,我們可以看到,當父組件重新渲染時,即使傳遞給子組件的參數(shù)保持不變,子組件也會重新渲染。這將導致子組件的虛擬 DOM 與以前的虛擬 DOM 執(zhí)行差異檢查。由于我們的子組件中沒有變更且重新渲染時的所有 props 都沒有變,所以真正的 DOM 不會被更新。

真正的 DOM 不會進行不必要地更新對性能確實是有好處,但是我們可以看到,即使子組件中沒有實際更改,也會創(chuàng)建新的虛擬 DOM 并執(zhí)行差異檢查。對于小型 React 組件,這種性能消耗可以忽略不計,但對于大型組件,性能影響會很大。為了避免這種重新渲染和虛擬 DOM 的差異檢查,我們使用 Memoization。

React 中的 Memoization

在 React 應用的上下文中,Memoization 是一種手段,每當父組件重新渲染時,子組件僅在它所依賴的 props 發(fā)生變化時才會重新渲染。如果子組件所依賴的 props 中沒有更改,則它不會執(zhí)行 render 方法,并將返回緩存的結(jié)果。由于渲染方法未執(zhí)行,因此不會有虛擬 DOM 創(chuàng)建和差異檢查,從而實現(xiàn)性能的提升。

現(xiàn)在,讓我們看看如何在類和函數(shù)組件中實現(xiàn) Memoization,以避免這種不必要的重新渲染。

類組件實現(xiàn) Memoization

為了在類組件中實現(xiàn) Memoization,我們將使用 React.PureComponent。React.PureComponent 實現(xiàn)了 shouldComponentUpdate(),它對 stateprops 進行了淺比較,并且僅在 props 或 state 發(fā)生更改時才重新渲染 React 組件。

將子組件更改為如下所示的代碼:

//Child.js
class Child extends React.PureComponent { // 這里我們把 React.Component 改成了 React.PureComponent
  render() {
    console.log("Child render");
    return (
      <div>
        <h3>{this.props.name}</h3>
      </div>
    );
  }
}

export default Child;

此示例的完整代碼顯示在這個 sandbox 中。

父組件保持不變。現(xiàn)在,當我們在父組件中增加 count 時,控制臺中的輸出如下所示:

Parent render
Child render
Parent render
Parent render

對于首次渲染,它同時調(diào)用父組件和子組件的 render 方法。

對于每次增加 count 后的重新渲染,僅調(diào)用父組件的 render 函數(shù)。子組件不會重新渲染。

函數(shù)組件實現(xiàn) Memoization

為了在函數(shù)組件中實現(xiàn) Memoization,我們將使用 React.memo()。React.memo() 是一個高階組件(HOC),它執(zhí)行與 PureComponent 類似的工作,來避免不必要的重新渲染。

以下是函數(shù)組件的代碼:

//Child.js
export function Child(props) {
  console.log("Child render");
  return (
    <div>
      <h3>{props.name}</h3>
    </div>
  );
}

export default React.memo(Child); // 這里我們給子組件添加 HOC 實現(xiàn) Memoization

同時還將父組件轉(zhuǎn)換為了函數(shù)組件,如下所示:

//Parent.js
export default function Parent() {
  const [count, setCount] = useState(0);
  const handleClick = () => {
    setCount(count + 1);
  };
  console.log("Parent render");
  return (
    <div>
      <button onClick={handleClick}>Increment</button>
      <h3>{count}</h3>
      <Child name={"joe"} />
    </div>
  );
}

此示例的完整代碼可以在這個 sandbox 中看到。

現(xiàn)在,當我們遞增父組件中的 count 時,以下內(nèi)容將輸出到控制臺:

Parent render
Child render
Parent render
Parent render
Parent render

React.memo() 存在的問題

在上面的示例中,我們看到,當我們對子組件使用 React.memo() HOC 時,子組件沒有重新渲染,即使父組件重新渲染了。

但是,需要注意的一個小問題是,如果我們將函數(shù)作為參數(shù)傳遞給子組件,即使在使用 React.memo() 之后,子組件也會重新渲染。讓我們看一個這樣的例子。

我們將更改父組件,如下所示。在這里,我們添加了一個處理函數(shù),并作為參數(shù)傳遞給子組件:

//Parent.js
export default function Parent() {
  const [count, setCount] = useState(0);
  const handleClick = () => {
    setCount(count + 1);
  };

  const handler = () => {
    console.log("handler");    // 這里的 handler 函數(shù)將會被傳遞給子組件
  };

  console.log("Parent render");
  return (
    <div className="App">
      <button onClick={handleClick}>Increment</button>
      <h3>{count}</h3>
      <Child name={"joe"} childFunc={handler} />
    </div>
  );
}

子組件代碼將保持原樣。我們不會在子組件中使用父組件傳遞來的函數(shù):

//Child.js
export function Child(props) {
  console.log("Child render");
  return (
    <div>
      <h3>{props.name}</h3>
    </div>
  );
}

export default React.memo(Child);

現(xiàn)在,當我們遞增父組件中的 count 時,它會重新渲染并同時重新渲染子組件,即使傳遞的參數(shù)中沒有更改。

那么,是什么原因?qū)е伦咏M件重新渲染的呢?答案是,每次父組件重新渲染時,都會創(chuàng)建一個新的 handler 函數(shù)并將其傳遞給子組件?,F(xiàn)在,由于每次重新渲染時都會重新創(chuàng)建 handle 函數(shù),因此子組件在對 props 進行淺比較時會發(fā)現(xiàn) handler 引用已更改,并重新渲染子組件。

接下來,我們將介紹如何解決此問題。

通過 useCallback() 來避免更多的重復渲染

導致子組件重新渲染的主要問題是重新創(chuàng)建了 handler 函數(shù),這更改了傳遞給子組件的引用。因此,我們需要有一種方法來避免這種重復創(chuàng)建。如果未重新創(chuàng)建 handler 函數(shù),則對 handler 函數(shù)的引用不會更改,因此子組件不會重新渲染。

為了避免每次渲染父組件時都重新創(chuàng)建函數(shù),我們將使用一個名為 useCallback() 的 React Hook。Hooks 是在 React 16 中引入的。要了解有關 Hooks 的更多信息,你可以查看 React 的官方 hooks 文檔,或者查看 `React Hooks: How to Get Started & Build Your Own"。

useCallback() 鉤子傳入兩個參數(shù):回調(diào)函數(shù)和依賴項列表。

以下是 useCallback() 示例:

const handleClick = useCallback(() => {
  //Do something
}, [x,y]);

在這里,useCallback() 被添加到 handleClick() 函數(shù)中。第二個參數(shù) [x, y] 可以是空數(shù)組、單個依賴項或依賴項列表。每當?shù)诙€參數(shù)中提到的任何依賴項發(fā)生更改時,才會重新創(chuàng)建 handleClick() 函數(shù)。

如果 useCallback() 中提到的依賴項沒有更改,則返回作為第一個參數(shù)提及的回調(diào)函數(shù)的 Memoization 版本。我們將更改父組件,以便對傳遞給子組件的處理程序使用 useCallback() 鉤子:

//Parent.js
export default function Parent() {
  const [count, setCount] = useState(0);
  const handleClick = () => {
    setCount(count + 1);
  };

  const handler = useCallback(() => { // 給 handler 函數(shù)使用 useCallback()
    console.log("handler");
  }, []);

  console.log("Parent render");
  return (
    <div className="App">
      <button onClick={handleClick}>Increment</button>
      <h3>{count}</h3>
      <Child name={"joe"} childFunc={handler} />
    </div>
  );
}

子組件代碼將保持原樣。

此示例的完整代碼這個 sandbox 中。

當我們在上述代碼的父組件中增加 count 時,我們可以看到以下輸出:

Parent render
Child render
Parent render
Parent render
Parent render

由于我們對父組件中的 handler 使用了 useCallback() 鉤子,因此每次父組件重新渲染時,都不會重新創(chuàng)建 handler 函數(shù),并且會將 handler 的 Memoization 版本傳遞到子組件。子組件將進行淺比較,并注意到 handler 函數(shù)的引用沒有更改,因此它不會調(diào)用 render 方法。

值得注意的事

Memoization 是一種很好的手段,可以避免在組件的 state 或 props 沒有改變時對組件進行不必要的重新渲染,從而提高 React 應用的性能。你可能會考慮為所有組件添加 Memoization,但這并不一定是構(gòu)建高性能 React 組件的方法。只有在組件出現(xiàn)以下情況時,才應使用 Memoization:

  • 固定的輸入有固定的輸出時

  • 具有較多 UI 元素,虛擬 DOM 檢查將影響性能

  • 多次傳遞相同的參數(shù)

“如何利用Memoization提高React性能”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關的知識可以關注創(chuàng)新互聯(lián)網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!

網(wǎng)站欄目:如何利用Memoization提高React性能
網(wǎng)頁地址:http://bm7419.com/article4/jjcooe.html

成都網(wǎng)站建設公司_創(chuàng)新互聯(lián),為您提供虛擬主機網(wǎng)站設計公司、用戶體驗、移動網(wǎng)站建設、網(wǎng)站內(nèi)鏈、做網(wǎng)站

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

綿陽服務器托管