![](/img/trans.png)
[英]React Native - FlatLists re-renders memoized components (React.memo not working)
[英]Why React re-renders unnecessarily child components, sometimes even memoized ones?
我遇到了这个线程,但我在这里问了一个更微妙的问题。 为什么 React 通常会重新渲染子组件,有时甚至在使用useMemo
钩子时也是如此?
在下面的示例中,我希望Child
和Mchild
组件不会在输入 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.