admin管理员组文章数量:1279148
I came across this answer, which appears to address this: When does React re-render child ponent?
But I'm asking a little more nuanced question here. Why does React re-render child ponents (generally) when using the useMemo
hook?
In the below example, I'd expect both Child
and Mchild
ponents not re-render on an input onChange event, but only Mchild
doesn't re-render. Child
renders on every keypress.
Could anyone shed some light on why React does this? I suppose what I'm asking is, I don't understand why React doesn't do this by default. What would be the disadvantage of using a child ponent pattern that always uses React.memo
?
import React, { useMemo, useState } from "react";
const Child = ({ person, which }: any) => {
console.log(`${which} render`);
return <div>{`From child ponent: ${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;
I came across this answer, which appears to address this: When does React re-render child ponent?
But I'm asking a little more nuanced question here. Why does React re-render child ponents (generally) when using the useMemo
hook?
In the below example, I'd expect both Child
and Mchild
ponents not re-render on an input onChange event, but only Mchild
doesn't re-render. Child
renders on every keypress.
Could anyone shed some light on why React does this? I suppose what I'm asking is, I don't understand why React doesn't do this by default. What would be the disadvantage of using a child ponent pattern that always uses React.memo
?
import React, { useMemo, useState } from "react";
const Child = ({ person, which }: any) => {
console.log(`${which} render`);
return <div>{`From child ponent: ${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;
Share
Improve this question
edited Jan 20, 2023 at 19:45
Gary
asked Jan 17, 2023 at 2:32
GaryGary
1,0171 gold badge15 silver badges33 bronze badges
10
- I posted an answer to explain things from the ground up. Please check my latest edit and let me know :) – Youssouf Oumar Commented Jan 17, 2023 at 17:42
- Thank you Yousoumar. So to summarize your answer -- React doesn't use React.memo by default b/c it's more expensive than just allowing the child ponents to re-render, unless of course it isn't, at which point you'd use React.memo explicitly? – Gary Commented Jan 18, 2023 at 2:06
- My pleasure @Gary :) I have added one last case that I did not mention before when your memorized ponent wraps another ponent. Check it and let me know! – Youssouf Oumar Commented Jan 18, 2023 at 10:12
- How do you measure how expensive a re-render is vs. a React.memo invocation? – Gary Commented Jan 18, 2023 at 23:33
- 1 I gave more thought to this and here's my conclusion -- we're evaluating O(number of shallow parisons [one per prop]) vs. a re-render of the child ponent; I'm fairly confident that a re-render of the child ponent is more pute intensive than a few shallow parisons so I'm still not sure why React.memo isn't the default. Maybe it's an issue of practicality -- that most apps wouldn't use the useMemo hook for props so they opted for full child re-renders every time. – Gary Commented Jan 21, 2023 at 3:49
2 Answers
Reset to default 10A ponent re-renders when its internal state changes or its parent re-render. 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 parison algorithm, which is not free, as Dan Abramov, one of the maintainers says:
Shallow parisons aren’t free. They’re O(prop count). And they only buy something if it bails out. All parisons where we end up re-rendering are wasted.
Why would you expect always paring to be faster? Considering many ponents 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>
<p>Default rendering behavior overview </p>
<SimpleChild />
<button onClick={() => setState((prev) => !prev)}>Render Parent</button>
</div>
);
}
ReactDOM.render(
<Parent />,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare./ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare./ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
A ponent re-rendering because of its parent re-rendering may be problematic, if this child does expansive puting 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
:
// 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>
<p>Memoizing with memo</p>
<HeavyChild />
<button onClick={() => setState((prev) => !prev)}>Render Parent</button>
</div>
);
}
ReactDOM.render(
<Parent />,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare./ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare./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
? Well, our memo
won't do what we want anymore. This is because memo
does "something like":
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.
// 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>
<p>Memoizing with memo when an object is passed as props</p>
<HeavyChild someObject={someObject} />
<button onClick={() => setState((prev) => !prev)}>Render Parent</button>
</div>
);
}
ReactDOM.render(
<Parent />,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare./ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare./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 es to objects being passed as props, you can use useMemo
, to memoize the object:
We have the same behavior as objects when it es to functions being passed as props, in which case we use
useCallback
to memoize them.
// 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>
<p>Memoizing with memo when a memoized object is passed as props</p>
<HeavyChild someMemoizedObject={someMemoizedObject} />
<button onClick={() => setState((prev) => !prev)}>Render Parent</button>
</div>
);
}
ReactDOM.render(
<Parent />,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare./ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare./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 ponent that can be consumed with children
, as in this case, children
is, as of now, always a new reference.
// Memoizing with memo when a ponent 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>
<p>Memoizing with memo when a ponent is passed as children</p>
<HeavyChild><SomeNestedChild/></HeavyChild>
<button onClick={() => setState((prev) => !prev)}>Render Parent</button>
</div>
);
}
ReactDOM.render(
<Parent />,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare./ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare./ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Let's see first when React ponents re-render. React children will re-render when either their props/state change or their parent ponent re-renders.
In your case, the Child will re-render since there is a state change in the parent ponent. Whenever you type in the input field, the state message changes, causing the parent ponent to re-render, hence the Child ponent will re-render.
const [message, setMessage] = useState<string>("");
In order to make your Child ponent only re-render when its prop changes, you need to memoize it, like you have done with MChild
. Now MChild
will only re-render when its prop changes, which is exactly the case of yours.
本文标签:
版权声明:本文标题:javascript - Why does React re-render child components even when passing props memoized with useMemo? - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1741250275a2365660.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论