reactjs 我可以只使用useCallback,或只使用memo,或两者都使用吗?

m4pnthwp  于 2023-05-06  发布在  React
关注(0)|答案(2)|浏览(101)

我正在阅读关于useCallbackmemo的React文档,如果我必须在下面的示例中同时使用这两个文档,我会有点困惑。
启动器代码:

function ProductPage({ productId, referrer, theme }) {
  // ...
  return (
    <div className={theme}>
      <ShippingForm onSubmit={handleSubmit} />
    </div>
);

从上面的代码中可以看出,切换theme属性会使应用程序冻结一段时间,但如果从JSX中删除<ShippingForm />,则会感觉很快。因此,我们必须优化ShippingForm组件,因为默认情况下,当组件重新呈现时,React会递归地重新呈现其所有子组件。因此,为了解决这个问题,我们可以告诉ShippingForm跳过重新渲染,当它的props与上次渲染相同时,通过将其 Package 在memo中,如下所示:

import { memo } from 'react';

    const ShippingForm = memo(function ShippingForm({ onSubmit }) {
      // ...
});

但是,如果handleSubmit函数是一个箭头函数或使用function关键字的常规函数,memo优化将不起作用,因为在JavaScript中,function () {}() => {}总是创建不同的函数,类似于{}对象文字总是创建新对象。因此,ShippingForm prop 将永远不会相同,memo优化也不会起作用。
下面的代码将不起作用

function ProductPage({ productId, referrer, theme }) {
  // Every time the theme changes, this will be a different function...
  function handleSubmit(orderDetails) {
    post('/product/' + productId + '/buy', {
      referrer,
      orderDetails,
    });
  }
  
  return (
    <div className={theme}>
      {/* ... so ShippingForm's props will never be the same, and it will re-render every time */}
      <ShippingForm onSubmit={handleSubmit} />
    </div>
  );
}

这也不行

function ProductPage({ productId, referrer, theme }) {
  // Every time the theme changes, this will be a different function...
  const handleSubmit = (orderDetails)=> {
    post('/product/' + productId + '/buy', {
      referrer,
      orderDetails,
    });
  }
  
  return (
    <div className={theme}>
      {/* ... so ShippingForm's props will never be the same, and it will re-render every time */}
      <ShippingForm onSubmit={handleSubmit} />
    </div>
  );
}

因此,为了解决这个问题,我们必须使用useCallback钩子,如下所示。
现在我的问题是,既然我们使用了useCallback钩子,我们是否仍然通过将memo Package 在memo中,来告诉ShippingForm跳过重新渲染,当它的props与上次渲染相同时?还是没必要?谢谢你帮忙

function ProductPage({ productId, referrer, theme }) {
  // Tell React to cache your function between re-renders...
  const handleSubmit = useCallback((orderDetails) => {
    post('/product/' + productId + '/buy', {
      referrer,
      orderDetails,
    });
  }, [productId, referrer]); // ...so as long as these dependencies don't change...

  return (
    <div className={theme}>
      {/* ...ShippingForm will receive the same props and can skip re-rendering */}
      <ShippingForm onSubmit={handleSubmit} />
    </div>
  );
}
iswrvxsc

iswrvxsc1#

是的,你应该两者都用。
P.S.你的问题中有一个小错误:useMemo是一个用于在组件内部存储值的钩子(类似于useCallback,但它保存函数执行的结果)。React.memo几乎是相同的东西,但组件本身。你问的是最后一件事。

rt4zxlrg

rt4zxlrg2#

所有的困惑都来自于你假设一个组件在接收不同的 prop 时呈现。实际上,组件仅在其内部状态更改或其父组件呈现时才呈现。这就是为什么这个没有props的Child组件在其父组件渲染时被再次调用的原因:

const Child = () => {
  console.log("Child with no props renders");
  return <div></div>;
};

const Parent = () => {
  const [state, setState] = React.useState(true);

  console.clear();
  console.log("Parent renders");
  
  
  return (
    <div>
      <button onClick={() => setState((prev) => !prev)}>Render parent</button>
      <Child />
    </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>

当这种行为有问题时,因为Child做了大量的计算,例如,你可以告诉React在memo的帮助下,只有当它接收到新的props时才渲染它:

const Child = React.memo(() => {
  console.log("Child with no props renders");
  return <div></div>;
});

const Parent = () => {
  const [state, setState] = React.useState(true);

  console.clear();
  console.log("Parent renders");
  
  return (
    <div>
      <button onClick={() => setState((prev) => !prev)}>Render parent</button>
      <Child />
    </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>

memo将比较以前的props和新的props(在渲染Parent时)。如果相同,则跳过渲染Child。无论您是否传递任何类似上面的或原始的属性,这都是有效的。然而,如果你传递一个在Parent的主体中定义的对象或函数,它们会被传递给不同的内存引用,因此我们使用useCallback来记忆函数,使用useMemo来记忆对象:

const ChildWithPrimitiveProp = React.memo(() => {
  console.log("ChildWithPrimitiveProps renders");
  return <div></div>;
});

const ChildWithNonMemoizedFunctionProp = React.memo(() => {
  console.log("ChildWithNonMemoizedFunctionProp renders");
  return <div></div>;
});

const ChildWithNonMemoizedObjectProp = React.memo(() => {
  console.log("ChildWithNonMemoizedObjectProp renders");
  return <div></div>;
});

const ChildWithMemoizedFunctionProp = React.memo(() => {
  console.log("ChildWithMemoizedFunctionProp renders");
  return <div></div>;
});

const ChildWithMemoizedObjectProp = React.memo(() => {
  console.log("ChildWithMemoizedObjectProp renders");
  return <div></div>;
});

const Parent = () => {
  const [state, setState] = React.useState(true);
  
  function fn(){};
  const obj = {};
  
  const memoizedFn = React.useCallback(fn, []);
  const memoizedObj = React.useMemo(()=>obj, []);
  
  console.clear();
  console.log("Parent renders");
  
  return (
    <div>
      <button onClick={() => setState((prev) => !prev)}>Render parent</button>
      <ChildWithPrimitiveProp message="Hello World" />
      <ChildWithNonMemoizedFunctionProp fn ={fn}/>
      <ChildWithNonMemoizedObjectProp fn ={obj}/>
      <ChildWithMemoizedFunctionProp fn ={memoizedFn}/>
      <ChildWithMemoizedObjectProp fn ={memoizedObj}/>
    </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>

因此,您需要两个或其中一个,具体取决于您的用例。

相关问题