简体   繁体   English

为什么 React 会重新渲染不必要的子组件,有时甚至是记忆化的组件?

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

I came across this thread , but I'm asking a little more nuanced question here.我遇到了这个线程,但我在这里问了一个更微妙的问题。 Why does React re-render child components, generally, and even when using the useMemo hook sometimes?为什么 React 通常会重新渲染子组件,有时甚至在使用useMemo钩子时也是如此?

In the below example, I'd expect both Child and Mchild components to not re-render on an input onChange event, but only Mchild doesn't re-render.在下面的示例中,我希望ChildMchild组件不会在输入 onChange 事件上重新呈现,但只有Mchild不会重新呈现。 Child renders on every key press. Child在每次按键时呈现。

Could anyone shed some light on why React does this?谁能解释一下为什么 React 会这样做? I suppose what I'm asking is, I don't understand why React doesn't do this by default.我想我要问的是,我不明白为什么 React 默认不这样做。 What would be the disadvantage of using a child component pattern that always uses React.memo ?使用始终使用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;

A component re-renders when its internal state changes or its parent re-render.组件在其内部 state 更改或其父级重新呈现时重新呈现。 React doesn't memoize everything by default because, first, most re-renders are not expansive, and second, to be able to memoize, you need a comparison algorithm, which is not free, as Dan Abramov , one of the maintainers says :默认情况下,React 不会记忆所有内容,因为首先,大多数重新渲染都不会扩展,其次,为了能够记忆,您需要一个比较算法,它不是免费的,正如其中一位维护者Dan Abramov所说

Shallow comparisons aren't free.肤浅的比较不是免费的。 They're O(prop count).他们是 O(prop count)。 And they only buy something if it bails out.他们只会在纾困时才买东西。 All comparisons where we end up re-rendering are wasted.我们最终重新渲染的所有比较都被浪费了。

Why would you expect always comparing to be faster?为什么你会期望总是比较更快? Considering many components always get different props.考虑到许多组件总是得到不同的道具。

 // 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>

A component re-rendering because of its parent re-rendering may be problematic, if this child does expansive computing tasks, without being affected by the parent re-render.如果这个子组件执行扩展计算任务,而不受父组件重新渲染的影响,那么由于其父组件重新渲染而导致的组件重新渲染可能会有问题。 In this case, you can tell React not to re-render this child when the parent re-renders, with memo :在这种情况下,你可以告诉 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>

Now, what happens if we pass an object to the above memoized HeavyChild ?现在,如果我们将 object 传递给上面记忆的HeavyChild会发生什么? Well, our memo won't do what we want anymore.好吧,我们的memo不会再做我们想做的事了。 This is because memo does "something like":这是因为memo做了“类似的事情”:

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

But an object defined in the parent gets re-created on a new reference on each re-render, so it's a new value.但是在父级中定义的 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>

If you wanna avoid the above behavior when it comes to objects being passed as props, you can use useMemo , to memoize the object:如果你想在将对象作为道具传递时避免上述行为,你可以使用useMemo来记住 object:

We have the same behavior as objects when it comes to functions being passed as props, in which case we useuseCallback to memoize them.当函数作为 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>

And the last thing to know is that memo won't work as expected if HeavyChild wrap a child component that can be consumed with children , as in this case, children is, as of now, always a new reference.最后要知道的是,如果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.

相关问题 React Native - FlatLists 重新呈现记忆化组件(React.memo 不起作用) - React Native - FlatLists re-renders memoized components (React.memo not working) 当父级重新渲染时,连接的 React 组件不必要地重新渲染 - Connected React component unnecessarily re-renders when parent re-renders 为什么 useDispatch 重新渲染父组件? - Why useDispatch re-renders parent components? 即使没有更改,React 也会重新渲染 - React re-renders even with no change 当我在 forms 中输入输入时,反应组件不必要地重新渲染 - React component unnecessarily re-renders when I enter input in forms 自定义 React Hook 是否应该导致依赖组件的重新渲染? - Should Custom React Hooks Cause Re-Renders of Dependent Components? React router render prop路径总是重新渲染组件 - React router render prop route always re-renders components React 的 Context API 重新渲染所有包装在 Context 中的组件,即使使用了 React.Memo API - React's Context API re-renders all components that are wrapped inside the Context even after using the React.Memo API 反应子组件重新渲染而不改变状态 - React child component Re-renders without state change 如果 object 引用相同,为什么要重新渲染组件? - Why react re-renders component if the object reference is the same?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM