繁体   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