为什么即使传递使用 useMemo 记忆的 props,React 也会重新渲染子组件?

Gar*_*ary 4 javascript reactjs

我遇到了这个答案,它似乎解决了这个问题:React何时重新渲染子组件?

但我在这里问一个更微妙的问题。为什么 React 在使用useMemohook 时(通常)会重新渲染子组件?

在下面的示例中,我希望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;
Run Code Online (Sandbox Code Playgroud)

You*_*mar 7

当组件的内部状态发生变化或其父组件重新渲染时,组件将重新渲染。默认情况下,React 不会记住所有内容,因为首先,大多数重新渲染都不是扩展性的,其次,为了能够记忆,您需要一个比较算法,而该算法不是免费的,正如维护者之一Dan Abramov所说

\n
\n

浅层比较是免费的。它们\xe2\x80\x99re O(prop count)。他们只会在企业纾困的情况下购买一些东西。我们最终重新渲染的所有比较都被浪费了。

\n

为什么你总是期望比较速度更快?考虑到许多组件总是会获得不同的道具。

\n
\n

\r\n
\r\n
// Default rendering behavior overview \n\nconst SimpleChild = () => {\n  console.log("SimpleChild render");\n  return <div></div>;\n};\n\nfunction Parent() {\n  const [state, setState] = React.useState(true);\n  console.clear();\n  console.log("Parent render");\n  return (\n    <div>\n      <p>Default rendering behavior overview </p>\n      <SimpleChild  />\n      <button onClick={() => setState((prev) => !prev)}>Render Parent</button>\n    </div>\n  );\n}\n\nReactDOM.render(\n  <Parent />,\n  document.getElementById("root")\n);
Run Code Online (Sandbox Code Playgroud)\r\n
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>\n<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>\n<div id="root"></div>
Run Code Online (Sandbox Code Playgroud)\r\n
\r\n
\r\n

\n

如果该子组件执行大量计算任务而不受父组件重新渲染的影响,则由于其父组件重新渲染而导致的组件重新渲染可能会出现问题。在这种情况下,您可以告诉 React 在父级重新渲染时不要重新渲染该子级,方法是memo

\n

\r\n
\r\n
// Memoizing with memo\n\nconst HeavyChild = React.memo(() => {\n  console.log("HeavyChild render");\n  return <div></div>;\n});\n\nfunction Parent() {\n  const [state, setState] = React.useState(true);\n  console.clear();\n  console.log("Parent render");\n  return (\n    <div>\n      <p>Memoizing with memo</p>\n      <HeavyChild  />\n      <button onClick={() => setState((prev) => !prev)}>Render Parent</button>\n    </div>\n  );\n}\n\nReactDOM.render(\n  <Parent />,\n  document.getElementById("root")\n);
Run Code Online (Sandbox Code Playgroud)\r\n
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>\n<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>\n<div id="root"></div>
Run Code Online (Sandbox Code Playgroud)\r\n
\r\n
\r\n

\n

现在,如果我们将一个对象传递给上面的 memoized 会发生什么HeavyChild?好吧,我们memo不会再做我们想做的事了。这是因为memo“类似”:

\n
if (prevPropsValue === currentPropsValue) { \n    // Don not re-render\n}\n
Run Code Online (Sandbox Code Playgroud)\n

但是在每次重新渲染时,父级中定义的对象都会在新引用上重新创建,因此它是一个新值。

\n

\r\n
\r\n
// Memoizing with memo when an object is passed as props\n\nconst HeavyChild = React.memo(() => {\n  console.log("HeavyChild render");\n  return <div></div>;\n});\n\nfunction Parent() {\n  const [state, setState] = React.useState(true);\n  const someObject = {}\n  console.clear();\n  console.log("Parent render");\n  return (\n    <div>\n      <p>Memoizing with memo when an object is passed as props</p>\n      <HeavyChild someObject={someObject}  />\n      <button onClick={() => setState((prev) => !prev)}>Render Parent</button>\n    </div>\n  );\n}\n\nReactDOM.render(\n  <Parent />,\n  document.getElementById("root")\n);
Run Code Online (Sandbox Code Playgroud)\r\n
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>\n<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>\n<div id="root"></div>
Run Code Online (Sandbox Code Playgroud)\r\n
\r\n
\r\n

\n

如果您想在将对象作为 props 传递时避免上述行为,您可以使用useMemo, 来记忆该对象:

\n
\n

当函数作为 props 传递时,我们有与对象相同的行为,在这种情况下我们用来useCallback记忆它们。

\n
\n

\r\n
\r\n
// Memoizing with memo when a memoized object is passed as props\n\nconst HeavyChild = React.memo(() => {\n  console.log("HeavyChild render");\n  return <div></div>;\n});\n\nfunction Parent() {\n  const [state, setState] = React.useState(true);\n  const someMemoizedObject = React.useMemo(()=>{}, [])\n  console.clear();\n  console.log("Parent render");\n  return (\n    <div>\n      <p>Memoizing with memo when a memoized object is passed as props</p>\n      <HeavyChild someMemoizedObject={someMemoizedObject}  />\n      <button onClick={() => setState((prev) => !prev)}>Render Parent</button>\n    </div>\n  );\n}\n\nReactDOM.render(\n  <Parent />,\n  document.getElementById("root")\n);
Run Code Online (Sandbox Code Playgroud)\r\n
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>\n<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>\n<div id="root"></div>
Run Code Online (Sandbox Code Playgroud)\r\n
\r\n
\r\n

\n

最后要知道的是,如果包装一个可以使用的子组件(就像在本例中一样),到目前为止,它始终是一个新引用,则不会memo按预期工作。HeavyChildchildrenchildren

\n

\r\n
\r\n
// Memoizing with memo when a component is passed as children\n\nconst SomeNestedChild = () => {\n  return <div></div>;\n};\n\nconst HeavyChild = React.memo(({children}) => {\n  console.log("HeavyChild render");\n  return <div></div>;\n});\n\nfunction Parent() {\n  const [state, setState] = React.useState(true);\n  \n  console.clear();\n  console.log("Parent render");\n  \n  return (\n    <div>\n      <p>Memoizing with memo when a component is passed as children</p>\n      <HeavyChild><SomeNestedChild/></HeavyChild>\n      <button onClick={() => setState((prev) => !prev)}>Render Parent</button>\n    </div>\n  );\n}\n\nReactDOM.render(\n  <Parent />,\n  document.getElementById("root")\n);
Run Code Online (Sandbox Code Playgroud)\r\n
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>\n<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>\n<div id="root"></div>
Run Code Online (Sandbox Code Playgroud)\r\n
\r\n
\r\n

\n