溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

如何利用Memoization提高React性能

發(fā)布時(shí)間:2022-03-30 09:36:45 來源:億速云 閱讀:133 作者:iii 欄目:web開發(fā)

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

如何利用Memoization提高React性能

React 是如何渲染視圖的?

在討論 React 中的 Memoization 細(xì)節(jié)之前,讓我們先來看看 React 是如何使用虛擬 DOM 渲染 UI 的。【相關(guān)推薦:Redis視頻教程】

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

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

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

根據(jù)這些差異,虛擬 DOM 能更高效地更新真正的 DOM。這樣提高了性能,因?yàn)樘摂M DOM 不會(huì)簡(jiǎn)單地更新 UI 元素及其所有子元素,而是有效地僅更新實(shí)際 DOM 中必要且最小的更改。

為什么需要 Memoization?

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

我們將創(chuàng)建一個(gè)父類,包含一個(gè)按鈕,用于遞增名為 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)建一個(gè) 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;

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

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

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

你可以在這個(gè) sandbox 中體驗(yàn)上述示例,并查看控制臺(tái)的輸出結(jié)果。

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

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

React 中的 Memoization

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

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

類組件實(shí)現(xiàn) Memoization

為了在類組件中實(shí)現(xiàn) Memoization,我們將使用 React.PureComponent。React.PureComponent 實(shí)現(xiàn)了 shouldComponentUpdate(),它對(duì) stateprops 進(jìn)行了淺比較,并且僅在 props 或 state 發(fā)生更改時(shí)才重新渲染 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;

此示例的完整代碼顯示在這個(gè) sandbox 中。

父組件保持不變?,F(xiàn)在,當(dāng)我們?cè)诟附M件中增加 count 時(shí),控制臺(tái)中的輸出如下所示:

Parent render
Child render
Parent render
Parent render

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

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

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

為了在函數(shù)組件中實(shí)現(xiàn) Memoization,我們將使用 React.memo()。React.memo() 是一個(gè)高階組件(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 實(shí)現(xiàn) Memoization

同時(shí)還將父組件轉(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>
  );
}

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

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

Parent render
Child render
Parent render
Parent render
Parent render

React.memo() 存在的問題

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

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

我們將更改父組件,如下所示。在這里,我們添加了一個(gè)處理函數(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ù)將會(huì)被傳遞給子組件
  };

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

子組件代碼將保持原樣。我們不會(huì)在子組件中使用父組件傳遞來的函數(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)在,當(dāng)我們遞增父組件中的 count 時(shí),它會(huì)重新渲染并同時(shí)重新渲染子組件,即使傳遞的參數(shù)中沒有更改。

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

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

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

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

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

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

以下是 useCallback() 示例:

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

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

如果 useCallback() 中提到的依賴項(xiàng)沒有更改,則返回作為第一個(gè)參數(shù)提及的回調(diào)函數(shù)的 Memoization 版本。我們將更改父組件,以便對(duì)傳遞給子組件的處理程序使用 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>
  );
}

子組件代碼將保持原樣。

此示例的完整代碼這個(gè) sandbox 中。

當(dāng)我們?cè)谏鲜龃a的父組件中增加 count 時(shí),我們可以看到以下輸出:

Parent render
Child render
Parent render
Parent render
Parent render

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

值得注意的事

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

  • 固定的輸入有固定的輸出時(shí)

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

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

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

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI