如何为自定义useFetchOnClick钩子添加请求中止功能?
实现请求自动中止,避免重复点击触发多请求
不需要彻底重构现有钩子,只需添加对未完成请求的追踪和自动中止逻辑即可解决问题。核心思路是用useRef保存当前请求的AbortController实例,在新请求发起前中止旧请求,并区分主动中止错误与真实请求错误。
修改后的完整代码
import { useReducer, useRef } from 'react'; const FetchType = { FETCH_START: 'FETCH_START', FETCH_SUCCESS: 'FETCH_SUCCESS', FETCH_ERROR: 'FETCH_ERROR' } const defaultState = { loading: false, error: false, success: false, nthFetch: 0, data: null } const fetchReducer = (state , action) => { switch(action.type){ case FetchType.FETCH_START: return { ...state, loading: true, error: false, success: false, nthFetch: state.nthFetch + 1 } case FetchType.FETCH_SUCCESS: return { ...state, loading: false, error: false, success: true, data: action.payload } case FetchType.FETCH_ERROR: return { ...state, loading: false, error: true, success: false, data: null } default: return state } } // the responseConversion function is used to convert the response to the desired format // for example `response => response.json()` or `response => response.blob()` const useFetchOnClick = (responseConversion) => { const [state, dispatch] = useReducer(fetchReducer, defaultState) // 用ref跨渲染周期保存当前未完成请求的控制器 const abortControllerRef = useRef(null) const fetchOnClick = (url, options) => { // 1. 中止之前未完成的请求 if (abortControllerRef.current) { abortControllerRef.current.abort(); } // 2. 创建新的请求控制器并保存到ref const abortController = new AbortController() const signal = abortController.signal abortControllerRef.current = abortController dispatch({ type: FetchType.FETCH_START }) fetch(url, { ...options, signal }) .then(responseConversion) .then(data => { dispatch({ type: FetchType.FETCH_SUCCESS, payload: data }) }) .catch(error => { // 仅当不是主动中止错误时,才更新错误状态 if (error.name !== 'AbortError') { dispatch({ type: FetchType.FETCH_ERROR }) console.log(error) } }) .finally(() => { // 请求完成后清空对应控制器,避免内存泄漏 if (abortControllerRef.current === abortController) { abortControllerRef.current = null } }) // 保留手动中止能力,组件可按需调用 return () => { if (abortControllerRef.current === abortController) { abortController.abort() abortControllerRef.current = null } } } return [state, fetchOnClick] }
关键修改说明
useRef追踪请求状态:用abortControllerRef存储当前未完成请求的控制器,ref的更新不会触发组件重渲染,适合跨周期追踪请求实例。- 自动中止旧请求:每次调用
fetchOnClick时,先检查是否有未完成的请求,若有则立即中止,避免多个请求同时执行导致状态混乱。 - 区分中止错误:
fetch被中止时会抛出AbortError,此时不更新错误状态,避免给用户展示错误提示。 - 清理资源:在请求完成(成功/失败/中止)后清空ref,防止残留过期控制器导致内存泄漏。
- 保留手动中止:仍返回中止函数,组件可在卸载或其他场景主动调用中止请求。
组件使用示例(无需修改)
export default function TestComponent() { const [state, fetchOnClick] = useFetchOnClick(response => response.json()) return ( <div> <p>test component</p> <button onClick={() => fetchOnClick("https://api.chucknorris.io/jokes/random")} > Chuck Norris Fact </button> { state.nthFetch === 0 ? <p>Press the button to get started</p> : state.loading ? <p>Loading...</p> : state.error ? <p>Error</p> : <p>{state.data.value}</p> } </div> ) }
内容的提问来源于stack exchange,提问作者Imaginary
相关产品推荐
相关产品推荐

