如何停止在 React 中重新渲染时进行 api 调用?

Posted

技术标签:

【中文标题】如何停止在 React 中重新渲染时进行 api 调用?【英文标题】:How to stop making api call on re-rendering in React? 【发布时间】:2022-01-24 05:17:17 【问题描述】:

在我的主页中,我有类似这样的代码

selectedTab===0 && <XList allItemList=some_list/>
selectedTab===1 && <YList allItemList=some_list2/>

现在,在 XList 中,我有这样的东西:

props.allItemList.map(item => <XItem item=item/>)

现在,在 XItem 内部,我正在调用一个 api 来获取 XItem 的图像。

现在我的问题是当在主页时,我将选项卡从 0 切换到 1 或从 1 切换到 0,它再次调用 XItem 中的所有 API。每当我切换标签时,它都会再次调用 api。我不想那样。我已经在 XItem 中使用了 useEffect,其中 [] 数组作为第二个参数。

我有我的后端代码,我在其中制作了一个 api 来获取 XItem 的图像。 API直接返回图片而不是url,所以我不能一次调用所有api。

我需要一些解决方案,以便最大限度地减少 api 调用。 感谢您的帮助。

【问题讨论】:

你需要了解useMemouseCallback react hook,google一下就行了。 看看 react-query,它优雅地解决了这些问题。 状态管理 【参考方案1】:

基本问题是,通过您选择所选选项卡的方式,您可以安装和卸载组件。重新挂载组件必然会重新运行任何挂载useEffect 回调,这些回调会发出网络请求并将任何结果存储在本地组件状态中。卸载组件必然会处理组件状态。

selectedTab === 0 && <XList allItemList=some_list />
selectedTab === 1 && <YList allItemList=some_list2 />

一种解决方案是将isActive 属性传递给XListYList,并根据selectedTab 值设置值。每个组件根据 isActive 属性有条件地呈现其内容。想法是保持组件安装,以便它们在最初安装时只获取一次数据。

<XList allItemList=some_list isActive=selectedTab === 0 />
<YList allItemList=some_list2 isActive=selectedTab === 1 />

例如XList

const XList = ( allItemList, isActive ) => 
  useEffect(() => 
    // expensive network call
  , []);

  return isActive 
    ? props.allItemList.map(item => <XItem item=item/>)
    : null;
;

替代方法包括将 API 请求和状态提升到父组件并作为 props 向下传递。或者使用 React 上下文来做同样的事情并通过上下文提供状态。或者实现/添加到像 Redux/Thunks 这样的全局状态管理。

【讨论】:

嗨@Drew,感谢您的回答。您的第一个解决方案效果很好。只是想知道如何使用 React Context 来实现这一点。如果我创建一个上下文,那么这意味着我需要一次调用所有 XItem 的所有 api。正如我所说,我正在调用 api 来获取图像,并且服务器正在以二进制文件而不是 url 形式发送图像,因此可能需要很长时间。目前,当我加载时,所有 XItem 都已加载,并定期显示图像。但是使用上下文后,它将等待所有图像加载一次。这是我的理解。如果我错了,请纠正我。【参考方案2】:

只是为了快速扩展Drew Reese 的答案,请考虑让您的选项卡成为选项卡组件的子级。这样一来,您的组件就(稍微)解耦了。

const MyTabulator = ( children ) => 
  const kids = React.useMemo(() => React.Children.toArray(children), [children]);
  const [state, setState] = React.useState(0);

  return (
    <div>
      kids.map((k, i) => (
        <button key=k.props.name onClick=() => setState(i)>
          k.props.name
        </button>
      ))
      kids.map((k, i) =>
        React.cloneElement(k, 
          key: k.props.name,
          isActive: i === state
        )
      )
    </div>
  );
;

以及处理 isActive 属性的包装器

const Tab = ( isActive, children ) => <div hidden=!isActive>children</div>

然后像这样渲染它们


<MyTabulator>
   <Tab name="x list"><XList allItemList=some_list /></Tab>
   <Tab name="y list"><YList allItemList=some_list2 /></Tab>
</MyTabulator>

【讨论】:

【参考方案3】:

我对这个问题的看法。您可以使用React.memo 包装XItem 组件:

const XItem = (props) => 
   ...


const areEqual = (prevProps, nextProps) => 
  /*
  Add your logic here to check if you want to rerender XItem 
  return true if you don't want rerender
  return false if you want a rerender
  */


export default React.memo(XItem, areEqual);

如果你选择使用useMemo,逻辑是一样的。

如果你想使用useCallback(我的默认选择)只需要包装你对 api 的调用。

【讨论】:

以上是关于如何停止在 React 中重新渲染时进行 api 调用?的主要内容,如果未能解决你的问题,请参考以下文章

当在 ReactJs React-Redux 中仅创建或更新列表中的一个项目时,如何停止重新渲染整个项目列表?

如何防止扩展的 React-Table 行在重新渲染时折叠?

如何在 React 中强制渲染组件

React 功能组件在重新挂载后停止渲染状态更改

使用 react-router-dom 停止页面中侧边栏的重新渲染

API 调用后 React/Flux seState 不重新渲染组件