[英]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.在下面的示例中,我希望
Child
和Mchild
组件不会在输入 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 use
useCallback
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.