多路由页面场景下,如何创建处理多端点GET/POST请求的自定义React Hook?
如何为多接口端点创建可复用的React Hook
你的两个思路各有可取之处,但也都存在可以优化的地方——思路1的通用性强但缺少Hook必备的状态管理,思路2的业务指向清晰但重复代码太多。结合两者的优势,我们可以构建一套「通用基础Hook + 业务封装Hook」的方案,既保证复用性,又能让业务代码直观易维护。
先给你拆解下具体实现:
第一步:打造通用的基础请求Hook
这个Hook会处理所有请求的共性逻辑:加载状态、错误捕获、请求头统一设置、请求方法适配,避免在每个业务请求里重复写这些代码。
import { useState, useEffect, useCallback } from 'react'; function useFetch(baseUrl = '/') { const [loading, setLoading] = useState(false); const [error, setError] = useState(null); // 通用请求函数,支持自定义请求方法、请求体、请求头 const request = useCallback(async (endpoint, options = {}) => { setLoading(true); setError(null); try { // 构建请求配置,默认POST/PUT带JSON格式的请求体 const requestConfig = { method: options.method || 'GET', headers: { 'Content-Type': 'application/json', ...options.headers, // 允许覆盖默认请求头 }, ...(['POST', 'PUT', 'PATCH'].includes(options.method) ? { body: JSON.stringify(options.body) } : {}), }; const response = await fetch(`${baseUrl}${endpoint}`, requestConfig); if (!response.ok) throw new Error(`请求失败:${response.statusText}`); return await response.json(); } catch (err) { setError(err.message); throw err; // 抛出错误,让调用方可以做页面级的错误处理 } finally { setLoading(false); } }, [baseUrl]); return { loading, error, request }; }
第二步:封装业务专属的Hook
基于上面的通用Hook,为水果、天气这类业务模块分别封装专属Hook,让调用方不用关心底层的接口地址和请求细节,只需要调用语义化的方法即可。
// 水果业务Hook function useFruitApi() { const { loading, error, request } = useFetch(); const fruitEndpoint = '/fruit'; // 获取水果列表 const getFruitList = useCallback(async () => { return await request(fruitEndpoint, { method: 'GET' }); }, [request, fruitEndpoint]); // 新增水果 const postFruit = useCallback(async (fruitData) => { return await request(fruitEndpoint, { method: 'POST', body: fruitData }); }, [request, fruitEndpoint]); return { loading, error, getFruitList, postFruit }; } // 天气业务Hook function useWeatherApi() { const { loading, error, request } = useFetch(); const weatherEndpoint = '/weather'; // 获取天气列表 const getWeatherList = useCallback(async () => { return await request(weatherEndpoint, { method: 'GET' }); }, [request, weatherEndpoint]); // 新增天气数据 const postWeather = useCallback(async (weatherData) => { return await request(weatherEndpoint, { method: 'POST', body: weatherData }); }, [request, weatherEndpoint]); return { loading, error, getWeatherList, postWeather }; }
在页面中使用的示例
比如在水果页面里,你可以这样调用:
function FruitPage() { const { loading, error, getFruitList, postFruit } = useFruitApi(); const [fruits, setFruits] = useState([]); // 页面加载时获取水果列表 useEffect(() => { const fetchFruits = async () => { try { const data = await getFruitList(); setFruits(data); } catch (err) { console.error('获取水果列表失败:', err); // 这里可以添加页面级的错误提示逻辑 } }; fetchFruits(); }, [getFruitList]); // 新增水果的处理函数 const handleAddFruit = async (newFruit) => { try { const addedFruit = await postFruit(newFruit); setFruits(prev => [...prev, addedFruit]); } catch (err) { console.error('新增水果失败:', err); } }; if (loading) return <div>加载水果列表中...</div>; if (error) return <div>出错了:{error}</div>; return ( <div> <h1>水果列表</h1> {/* 这里渲染水果列表和新增表单 */} </div> ); }
为什么这个方案更优?
- 复用性拉满:通用
useFetch处理了所有请求的共性逻辑,比如加载状态、错误处理、请求头设置,后续新增业务模块时,只需要封装新的业务Hook即可。 - 业务代码直观:
useFruitApi、useWeatherApi里的方法名语义化极强,调用方一看就知道是做什么的,不用关心底层的接口细节。 - 维护成本低:如果需要统一修改请求逻辑(比如添加授权头、调整错误提示),只需要改一次
useFetch;如果某个接口地址变更,只需要修改对应业务Hook里的endpoint即可。 - 符合Hook规则:所有自定义Hook都正确使用了
useState、useCallback等API,避免不必要的重渲染,完全遵循React的Hook规范。
对比你的两个思路:
- 思路1的问题:你的示例代码不符合React Hook规则(Hook里不能直接用
await,必须结合useEffect触发异步操作),而且缺少加载、错误状态的管理,调用方需要自己处理这些,容易重复写代码。 - 思路2的问题:每个请求函数都要重复写
fetch的逻辑,维护起来很麻烦,比如要加统一的请求头,就得逐个修改所有函数。
内容的提问来源于stack exchange,提问作者MiryangJung
相关产品推荐
相关产品推荐

