React中如何阻止同组件二次渲染时重复发起API请求
解决多实例SelectDropdown重复API请求的方案
要实现全平台复用的SelectDropdown组件,同时避免多个组件实例重复发起相同API请求,可以通过Context统一管理请求状态、缓存数据和请求Promise的方式解决,具体实现如下:
核心思路
用Context共享三个关键状态:
- 已缓存的下拉选项数据
- 请求加载状态
- 正在进行的请求Promise引用
当第一个组件发起请求时,后续组件会复用这个正在进行的Promise,不会重复调用API;请求完成后,所有组件共享缓存数据。
具体实现步骤
1. 创建DropdownContext
定义Context来共享数据和方法,方便所有组件实例访问:
import { createContext, useContext, useState, useCallback } from 'react'; // 定义Context类型(可根据实际业务调整) interface DropdownContextType { options: any[]; isLoading: boolean; fetchOptions: () => Promise<void>; } // 初始化Context默认值 const DropdownContext = createContext<DropdownContextType>({ options: [], isLoading: false, fetchOptions: async () => {}, }); // 自定义Hook简化Context调用 export const useDropdownContext = () => useContext(DropdownContext);
2. 实现Context Provider
在Provider内部管理请求逻辑,重点维护pendingPromise来拦截重复请求:
export const DropdownProvider = ({ children }: { children: React.ReactNode }) => { const [options, setOptions] = useState<any[]>([]); const [isLoading, setIsLoading] = useState(false); let pendingPromise: Promise<void> | null = null; // 保存正在进行的请求Promise const fetchOptions = useCallback(async () => { // 已有缓存数据,直接返回 if (options.length > 0) return; // 请求正在进行,复用已有Promise if (pendingPromise) return pendingPromise; setIsLoading(true); try { // 保存请求Promise,供后续组件复用 pendingPromise = getSelections().then((items) => { setOptions(items); }); await pendingPromise; } catch (error) { console.error('获取下拉选项失败:', error); setOptions([]); // 错误处理可根据业务调整 } finally { setIsLoading(false); pendingPromise = null; // 请求完成后清空引用 } }, [options]); return ( <DropdownContext.Provider value={{ options, isLoading, fetchOptions }}> {children} </DropdownContext.Provider> ); };
3. 修改DropdownWithOptions组件
让组件从Context获取数据和方法,不再独立管理请求:
import { useEffect } from 'react'; import { useDropdownContext } from './DropdownContext'; const DropdownWithOptions = (props: any) => { const { options, isLoading, fetchOptions } = useDropdownContext(); useEffect(() => { fetchOptions(); }, [fetchOptions]); // 加载状态可根据业务自定义(比如骨架屏、加载文字) if (isLoading) { return <div>加载中...</div>; } return <Dropdown options={options} {...props} />; };
4. 页面中使用Provider包裹组件
确保所有DropdownWithOptions实例处于同一个Context范围内:
import { DropdownProvider } from './DropdownContext'; const Page = () => { return ( <DropdownProvider> <DropdownWithOptions /> <DropdownWithOptions /> </DropdownProvider> ); };
方案优势
- 彻底解决多组件实例同时发起请求的问题:
pendingPromise会拦截后续重复请求,让所有组件等待同一个请求完成 - 数据共享:请求完成后所有组件直接复用缓存的
options - 低侵入性:组件本身无需关心请求逻辑,只需从Context获取数据即可
额外优化建议
- 如果需要支持不同类型的下拉选项(比如不同API接口),可以给
fetchOptions添加key参数,在Provider中用对象维护多个pendingPromises,区分不同请求 - 可添加缓存过期逻辑,比如记录数据更新时间戳,超过阈值后重新发起请求
内容的提问来源于stack exchange,提问作者noobHere
相关产品推荐
相关产品推荐

