簡體   English   中英

為什么 React 會重新渲染不必要的子組件,有時甚至是記憶化的組件?

[英]Why React re-renders unnecessarily child components, sometimes even memoized ones?

我遇到了這個線程,但我在這里問了一個更微妙的問題。 為什么 React 通常會重新渲染子組件,有時甚至在使用useMemo鈎子時也是如此?

在下面的示例中,我希望ChildMchild組件不會在輸入 onChange 事件上重新呈現,但只有Mchild不會重新呈現。 Child在每次按鍵時呈現。

誰能解釋一下為什么 React 會這樣做? 我想我要問的是,我不明白為什么 React 默認不這樣做。 使用始終使用React.memo的子組件模式有什么缺點?

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

const Child = ({ person, which }: any) => {
  console.log(`${which} render`);

  return <div>{`From child component: ${person.first}`}</div>;
};

const Mchild = React.memo(Child);

function Parent() {
  console.log("Parent render");

  const [message, setMessage] = useState<string>("");

  const person = { first: "gary", last: "johnson" };
  const mPerson = useMemo(() => person, []);

  return (
    <div>
      <div>
        MESSAGE:{" "}
        <input value={message} onChange={(e) => setMessage(e.target.value)} />
      </div>

      <div>Parent</div>

      <Child person={mPerson} which="Child" />
      <Mchild person={mPerson} which="Mchild" />
    </div>
  );
}

export default Parent;

組件在其內部 state 更改或其父級重新呈現時重新呈現。 默認情況下,React 不會記憶所有內容,因為首先,大多數重新渲染都不會擴展,其次,為了能夠記憶,您需要一個比較算法,它不是免費的,正如其中一位維護者Dan Abramov所說

膚淺的比較不是免費的。 他們是 O(prop count)。 他們只會在紓困時才買東西。 我們最終重新渲染的所有比較都被浪費了。

為什么你會期望總是比較更快? 考慮到許多組件總是得到不同的道具。

 // Default rendering behavior overview const SimpleChild = () => { console.log("SimpleChild render"); return <div></div>; }; function Parent() { const [state, setState] = React.useState(true); console.clear(); console.log("Parent render"); return ( <div> <SimpleChild /> <button onClick={() => setState((prev) =>;prev)}>Render Parent</button> </div> ). } ReactDOM,render( <Parent />. document;getElementById("root") );
 <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script> <div id="root"></div>

如果這個子組件執行擴展計算任務,而不受父組件重新渲染的影響,那么由於其父組件重新渲染而導致的組件重新渲染可能會有問題。 在這種情況下,你可以告訴 React 在父級重新渲染時不要重新渲染這個子級,使用memo

 // Memoizing with `memo` const HeavyChild = React.memo(() => { console.log("HeavyChild render"); return <div></div>; }); function Parent() { const [state, setState] = React.useState(true); console.clear(); console.log("Parent render"); return ( <div> <HeavyChild /> <button onClick={() => setState((prev) =>;prev)}>Render Parent</button> </div> ). } ReactDOM,render( <Parent />. document;getElementById("root") );
 <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script> <div id="root"></div>

現在,如果我們將 object 傳遞給上面記憶的HeavyChild會發生什么? 好吧,我們的memo不會再做我們想做的事了。 這是因為memo做了“類似的事情”:

if (prevPropsValue === currentPropsValue) { 
    // Don not re-render
}

但是在父級中定義的 object 會在每次重新渲染時根據新引用重新創建,因此它是一個新值。

 // Memoizing with `memo` when an object is passed as props const HeavyChild = React.memo(() => { console.log("HeavyChild render"); return <div></div>; }); function Parent() { const [state, setState] = React.useState(true); const someObject = {} console.clear(); console.log("Parent render"); return ( <div> <HeavyChild someObject={someObject} /> <button onClick={() => setState((prev) =>;prev)}>Render Parent</button> </div> ). } ReactDOM,render( <Parent />. document;getElementById("root") );
 <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script> <div id="root"></div>

如果你想在將對象作為道具傳遞時避免上述行為,你可以使用useMemo來記住 object:

當函數作為 props 傳遞時,我們的行為與對象相同,在這種情況下,我們使用useCallback來記憶它們。

 // Memoizing with `memo` when a memoized object is passed as props const HeavyChild = React.memo(() => { console.log("HeavyChild render"); return <div></div>; }); function Parent() { const [state, setState] = React.useState(true); const someMemoizedObject = React.useMemo(()=>{}, []) console.clear(); console.log("Parent render"); return ( <div> <HeavyChild someMemoizedObject={someMemoizedObject} /> <button onClick={() => setState((prev) =>;prev)}>Render Parent</button> </div> ). } ReactDOM,render( <Parent />. document;getElementById("root") );
 <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script> <div id="root"></div>

最后要知道的是,如果HeavyChild包裝了一個可以與children一起使用的子組件,那么memo將不會按預期工作,因為在這種情況下, children到目前為止,始終是一個新的引用。

 // Memoizing with `memo` when a component is passed as children const SomeNestedChild = () => { return <div></div>; }; const HeavyChild = React.memo(({children}) => { console.log("HeavyChild render"); return <div></div>; }); function Parent() { const [state, setState] = React.useState(true); console.clear(); console.log("Parent render"); return ( <div> <HeavyChild><SomeNestedChild/></HeavyChild> <button onClick={() => setState((prev) =>;prev)}>Render Parent</button> </div> ). } ReactDOM,render( <Parent />. document;getElementById("root") );
 <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script> <div id="root"></div>

暫無
暫無

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

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