簡體   English   中英

如何重寫這個簡單的組件樹以利用 React.memo?

[英]How to rewrite this simple component tree to take advantage of React.memo?

在下面的代碼沙箱中,我們有一個<Child />組件,它要么在<div />呈現,要么不在,取決於狀態。

import React, { useState, memo } from "react";

const Child = memo(
  ({ n }) => {
    console.log("Re-rendered child");
    return <span>Child {n}</span>;
  },
  () => true
);

export default function App() {
  const [shouldWrapChildComponent, setShouldWrapChildComponent] = useState(
    false
  );

  return (
    <div>
      <button
        onClick={() =>
          setShouldWrapChildComponent(
            (shouldWrapChildComponent) => !shouldWrapChildComponent
          )
        }
      >
        Toggle Wrapper
      </button>

      <br />

      {[0, 1, 2, 3, 4, 5].map((n) => {
        return shouldWrapChildComponent ? (
          <div>
            <Child n={n} />
          </div>
        ) : (
          <Child n={n} />
        );
      })}
    </div>
  );
}

如您所見, <Child />組件使用React.memo來防止重新渲染。

但是,當組件樹更改時不會阻止重新渲染(這是有道理的)。 如果shouldWrapChildComponenttrue ,則<Child />將在<div />內部渲染,否則<Child />將在<div />用於渲染的地方渲染。 這就是我所說的“組件樹更改”的意思。

有沒有辦法重寫組件,使得組件樹不會改變,從而可以利用 React.memo? 或者,考慮到我們總是需要有條件地包裝<Child />組件,有沒有辦法讓 React 保留<Child />的渲染輸出?

但是,當組件樹更改時不會阻止重新渲染(這是有道理的)。 如果 shouldWrapChildComponent 為 true,則將在 a 內部進行渲染,否則將在用於渲染的位置進行渲染。 這就是我所說的“組件樹更改”的意思

再說說上面的引文,我們做個小改動,去掉div元素:

{[0, 1, 2, 3, 4, 5].map((n) => {
        return shouldWrapChildComponent ? (
          <Child n={n} />
        ) : (
          <Child n={n} />
        );
      })
}

結果是:即使在切換按鈕后,您的Child組件也只會重新渲染 6 次。 您可以在App組件上放置一個 console.log() 並檢查控制台,切換按鈕將導致整個App重新渲染,但Child組件將不再重新渲染。

因此,記憶工作正常並防止子組件額外重新渲染。

但是當你用另一個元素(這里用div )包裝你的Child組件時,問題就出現了,為什么?

為了檢查這種行為,我通過創建一個resultArray變量來將主要部分與返回方法分開,以便在渲染之前記錄resultArray

import React, { useState, memo } from "react";

const Child = memo(
  ({ n }) => {
    console.log("Re-rendered child");
    return <span>Child {n}</span>;
  },
  () => true
);

export default function App() {
  const [shouldWrapChildComponent, setShouldWrapChildComponent] = useState(
    false
  );

  const resultArray = [0, 1, 2, 3, 4, 5].map((n) => {
        return shouldWrapChildComponent ? (
          <div>
            <Child n={n} />
          </div>
        ) : (
          <Child n={n} />
        );
      })

  console.log(`When shouldWrapChildComponent is ${shouldWrapChildComponent} the result array is: `, resultArray);

  return (
    <div>
      <button
        onClick={() =>
          setShouldWrapChildComponent(
            (shouldWrapChildComponent) => !shouldWrapChildComponent
          )
        }
      >
        Toggle Wrapper
      </button>
      <br />
      {resultArray}
    </div>
  );
}

shouldWrapChildComponentfalse時的resultArray

>(6) [Object, Object, Object, Object, Object, Object]

>0: Object
  type: "div"  // pay attention here
  key: null
  ref: null
  >props: Object
     n: 0         // ----> passing n via props to Child
  _owner: FiberNode
  _store: Object
>1: Object
>2: Object
>3: Object
>4: Object
>5: Object

shouldWrapChildComponenttrue時的resultArray

>(6) [Object, Object, Object, Object, Object, Object]

>0: Object
  type: null    // pay attention here
  key: null
  ref: null
  >props: Object     // -----> passing a object instead of Child
    >children: Object
      >type: Object
        >type:   ƒ _c() {}
        >compare: f () {}
  _owner: FiberNode
  _store: Object
>1: Object
>2: Object
>3: Object
>4: Object
>5: Object

如您所見,結果完全不同,因此每次在 DOM 中繪制新元素時,react 都會觸發重新渲染。

反應協調算法

當比較兩棵樹時,React 首先比較兩個根元素。 行為因根元素的類型而異。

因此,隨着元素type的改變, Reconciliation算法會標記它並嘗試重新渲染它,這種重新渲染就像組件安裝之前的第一次渲染一樣(並導致額外的重新渲染與您的子元素)。

更多關於 React 官方文檔

作為最簡單的解決方案

有很多解決方案可以避免這種額外的重新渲染,但作為一個簡單的解決方案,您可以更改主要部分,如下所示:

{[0, 1, 2, 3, 4, 5].map((n) => {
        return shouldWrapChildComponent ? (
          <div>
            <Child n={n} />
          </div>
        ) : (
          <div style={{display: "inline"}}>
            <Child n={n} />
          </div>
        );
      })
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM