如何停止在 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 调用。 感谢您的帮助。
【问题讨论】:
你需要了解useMemo
和useCallback
react hook,google一下就行了。
看看 react-query,它优雅地解决了这些问题。
状态管理
【参考方案1】:
基本问题是,通过您选择所选选项卡的方式,您可以安装和卸载组件。重新挂载组件必然会重新运行任何挂载useEffect
回调,这些回调会发出网络请求并将任何结果存储在本地组件状态中。卸载组件必然会处理组件状态。
selectedTab === 0 && <XList allItemList=some_list />
selectedTab === 1 && <YList allItemList=some_list2 />
一种解决方案是将isActive
属性传递给XList
和YList
,并根据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 行在重新渲染时折叠?