首页 > 解决方案 > 嵌套组件中的 React.memo 从不调用 areEqual,总是重新渲染

问题描述

我对 React.memo 今天的行为方式感到好奇。

似乎 React.memo 在嵌套函数式组件中不起作用,但在组件主返回和返回 React.memo 组件的嵌套函数中起作用。

这是预期的行为还是有其他方法可以使 React.memo 与嵌套的功能组件一起工作?

https://codesandbox.io/s/sweet-bhaskara-kbnlg?file=/src/memoTest.js:0-451

const MemoTestBase = ({ name, counter }) => {
  console.log("rendering", name);
  return (
    <div className="">
      <p>
        {name}: {counter}
      </p>
    </div>
  );
};

function areEqual(prevProps, nextProps) {
  const isEqual = prevProps.name === nextProps.name;

  if (isEqual) {
    console.log("stopping", prevProps.name);
  }

  return isEqual;
}

const MemoTest = React.memo(MemoTestBase, areEqual);


const { useState } = React;

function App() {
  const [counter, setCounter] = useState(0);

  function funOne() {
    return <MemoTest name="inside function" counter={counter} />;
  }

  const ComponentOne = () => {
    return <MemoTest name="inside component" counter={counter} />;
  };

  return (
    <div className="App">
      <h1>React.memo render test</h1>
      <p>Count: {counter}</p>
      <button onClick={() => setCounter(counter + 1)}>Count + 1</button>
      <hr />
      <p>Memo tests</p>
      <MemoTest name="root" counter={counter} /> {/* works! */}
      <ComponentOne /> {/* doesn't work */}
      {ComponentOne()} {/* works! */}
      {funOne()} {/* works! */}
    </div>
  );
}
ReactDOM.render(<App />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div class='react'></div>

标签: javascriptreactjs

解决方案


这里的问题是您在组件内部定义了 ComponentOne。因此,当 React 尝试重新渲染时,它会发现先前重新渲染中的 ComponentOne 引用与当前重新渲染中的引用不同,因此会重新安装该组件。因此备忘录不起作用

但是,当您像函数一样调用它时,它会返回相同的实例MemoTest,因此应该会发生重新渲染,memo因为道具不会改变

为了使它像您当前定义的那样工作,您应该将功能组件定义移出 App 组件

const MemoTestBase = ({ name, counter }) => {
  console.log("rendering", name);
  return (
    <div className="">
      <p>
        {name}: {counter}
      </p>
    </div>
  );
};

function areEqual(prevProps, nextProps) {
  const isEqual = prevProps.name === nextProps.name;

  if (isEqual) {
    console.log("stopping", prevProps.name);
  }

  return isEqual;
}

const MemoTest = React.memo(MemoTestBase, areEqual);


const ComponentOne = ({counter}) => {
  return <MemoTest name="inside component" counter={counter} />;
};

const { useState } = React;

function App() {
  const [counter, setCounter] = useState(0);

  function funOne() {
    return <MemoTest name="inside function" counter={counter} />;
  }

  return (
    <div className="App">
      <h1>React.memo render test</h1>
      <p>Count: {counter}</p>
      <button onClick={() => setCounter(counter + 1)}>Count + 1</button>
      <hr />
      <p>Memo tests</p>
      <MemoTest name="root" counter={counter} /> {/* works! */}
      <ComponentOne counter={counter}/> {/* doesn't work */}
      {ComponentOne({counter})} {/* works! */}
      {funOne()} {/* works! */}
    </div>
  );
}
ReactDOM.render(<App />, document.querySelector('.react'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div class='react'></div>


推荐阅读