如何让React自定义useFetch钩子在相同URL时重新发起请求?
解决useFetch重复点击同一URL不发起请求的问题
下面提供几种比添加时间戳更优雅的解决方案:
方案一:添加手动触发依赖项
修改useFetch钩子,新增一个trigger参数作为依赖,每次需要刷新时更新这个参数即可强制发起请求:
export const useFetch = <T extends unknown>( url: string, options?: RequestInit, trigger?: number ) => { const [loading, setLoading] = useState(false); const [error, setError] = useState(false); const [response, setResponse] = useState<T>(); useEffect(() => { if (!url) return; const controller = new AbortController(); setLoading(true); fetch(url, { ...options, signal: controller.signal }) .then((res) => { setError(!res.ok); return res.json(); }) .then((json) => { setLoading(false); setResponse((json as unknown as JSONResponse).content as T); }) .catch((err) => { // 忽略请求中止的错误 if (err.name !== 'AbortError') { console.log(err); setLoading(false); setError(true); } }); return () => controller.abort(); }, [url, options, trigger]); return { loading, error, response }; };
在组件中使用时,维护一个触发计数器,点击时更新计数器:
const [url, setUrl] = useState(''); const [fetchTrigger, setFetchTrigger] = useState(0); const roomIDRef = useRef() as React.MutableRefObject<HTMLInputElement>; const { error, response, loading } = useFetch<ExistsRoom>(url, undefined, fetchTrigger); const joinRoom = () => { const id = roomIDRef.current.value; if (!id) return; setUrl(`http://localhost:8808/api/v1/room/exists?id=${id}`); // 更新触发值,强制useEffect重新执行 setFetchTrigger(prev => prev + 1); };
方案二:暴露手动刷新函数
在useFetch内部封装一个reload函数,通过useCallback保证引用稳定,外部直接调用即可发起请求:
export const useFetch = <T extends unknown>( url: string, options?: RequestInit ) => { const [loading, setLoading] = useState(false); const [error, setError] = useState(false); const [response, setResponse] = useState<T>(); // 用ref保存最新的options,避免依赖变化导致useCallback频繁更新 const optionsRef = useRef(options); useEffect(() => { optionsRef.current = options; }, [options]); const fetchData = useCallback(async () => { if (!url) return; const controller = new AbortController(); setLoading(true); try { const res = await fetch(url, { ...optionsRef.current, signal: controller.signal }); setError(!res.ok); const json = await res.json(); setResponse((json as unknown as JSONResponse).content as T); } catch (err) { if ((err as Error).name !== 'AbortError') { console.log(err); setError(true); } } finally { setLoading(false); } return () => controller.abort(); }, [url]); // 初始加载 useEffect(() => { fetchData(); }, [fetchData]); return { loading, error, response, reload: fetchData }; };
组件中调用reload函数即可发起请求:
const [url, setUrl] = useState(''); const roomIDRef = useRef() as React.MutableRefObject<HTMLInputElement>; const { error, response, loading, reload } = useFetch<ExistsRoom>(url); const joinRoom = () => { const id = roomIDRef.current.value; if (!id) return; setUrl(`http://localhost:8808/api/v1/room/exists?id=${id}`); // 直接调用reload发起请求,不受url是否变化影响 reload(); };
方案三:完全由外部控制请求时机
如果不需要钩子自动发起初始请求,只在用户操作时触发,可以调整钩子逻辑,将请求控制权完全交给外部:
export const useFetch = <T extends unknown>() => { const [loading, setLoading] = useState(false); const [error, setError] = useState(false); const [response, setResponse] = useState<T>(); const fetchData = useCallback(async (url: string, options?: RequestInit) => { const controller = new AbortController(); setLoading(true); try { const res = await fetch(url, { ...options, signal: controller.signal }); setError(!res.ok); const json = await res.json(); setResponse((json as unknown as JSONResponse).content as T); } catch (err) { if ((err as Error).name !== 'AbortError') { console.log(err); setError(true); } } finally { setLoading(false); } return () => controller.abort(); }, []); return { loading, error, response, fetchData }; };
组件中直接调用fetchData:
const roomIDRef = useRef() as React.MutableRefObject<HTMLInputElement>; const { error, response, loading, fetchData } = useFetch<ExistsRoom>(); const joinRoom = () => { const id = roomIDRef.current.value; if (!id) return; const url = `http://localhost:8808/api/v1/room/exists?id=${id}`; // 每次点击都直接发起请求 fetchData(url); };
方案选择建议
- 若需要保留钩子的自动加载能力,同时支持手动刷新,优先选方案二;
- 若只需要简单的强制刷新触发,方案一实现成本最低;
- 若不需要自动加载,完全由用户操作触发请求,选方案三更简洁。
内容的提问来源于stack exchange,提问作者DerPenz
相关产品推荐
相关产品推荐

