React项目Fetch封装:异步函数使用Context的问题及替代方案
解决方案
一、解决Fetch拦截器中无法直接使用Context的问题
方案1:将封装的Fetch改为自定义Hook
useContext只能在React组件或自定义Hook内调用,把你的customFetcher包装成自定义Hook,就能直接在内部获取Context,不用每次请求传参数:
import React from 'react'; import { UserContext } from './path/to/UserContext'; const useCustomFetcher = () => { const { JWT, setJWT } = React.useContext(UserContext); const customFetcher = async (url, config = {}) => { // 合并用户传入的headers,避免覆盖原有配置 const requestConfig = { ...config, headers: { ...config.headers, Authorization: `Bearer ${JWT}`, }, }; let { response, data } = await originalRequest(url, requestConfig); // 用状态码判断未授权,比statusText更可靠 if (response.status === 401) { try { const refreshRes = await refreshToken(); // 修正原代码语法错误:需要await解析JSON const { accessJWT } = await refreshRes.json(); setJWT(accessJWT); const newConfig = { ...requestConfig, headers: { ...requestConfig.headers, Authorization: `Bearer ${accessJWT}`, }, }; const newResponse = await originalRequest(url, newConfig); response = newResponse.response; data = newResponse.data; } catch (err) { // 刷新失败处理:比如跳转登录页、清除用户状态 console.error('令牌刷新失败:', err); throw err; } } return { response, data }; }; return customFetcher; };
组件中使用方式:
const MyComponent = () => { const fetcher = useCustomFetcher(); const loadData = async () => { try { const { data } = await fetcher('/api/resource'); // 处理返回数据 } catch (err) { // 处理请求错误 } }; return <button onClick={loadData}>加载数据</button>; };
方案2:全局JWT管理器(保持Fetch为普通函数)
如果不想把Fetch改成Hook,可以单独写一个JWT管理模块,同步Context中的状态,这样普通的Fetch函数就能直接调用:
- 创建
jwtManager.js:
// 存储当前JWT和更新回调 let currentJWT = null; let updateJWTCallback = null; // 初始化管理器,从Context传入初始值和更新方法 export const initJWTManager = (initialJWT, callback) => { currentJWT = initialJWT; updateJWTCallback = callback; }; // 获取当前JWT export const getCurrentJWT = () => currentJWT; // 更新JWT并同步到Context export const updateJWT = (newJWT) => { currentJWT = newJWT; if (updateJWTCallback) { updateJWTCallback(newJWT); } };
- 在根组件初始化管理器:
import React, { useEffect } from 'react'; import { UserContext } from './path/to/UserContext'; import { initJWTManager } from './jwtManager'; const App = () => { const { JWT, setJWT } = React.useContext(UserContext); useEffect(() => { initJWTManager(JWT, setJWT); }, [JWT, setJWT]); return {/* 应用内容 */}; };
- 改写你的Fetch封装:
import { getCurrentJWT, updateJWT } from './jwtManager'; let customFetcher = async (url, config = {}) => { const jwt = getCurrentJWT(); const requestConfig = { ...config, headers: { ...config.headers, Authorization: `Bearer ${jwt}`, }, }; let { response, data } = await originalRequest(url, requestConfig); if (response.status === 401) { try { const refreshRes = await refreshToken(); const { accessJWT } = await refreshRes.json(); updateJWT(accessJWT); const newConfig = { ...requestConfig, headers: { ...requestConfig.headers, Authorization: `Bearer ${accessJWT}`, }, }; const newResponse = await originalRequest(url, newConfig); response = newResponse.response; data = newResponse.data; } catch (err) { console.error('令牌刷新失败:', err); throw err; } } return { response, data }; };
这样组件里可以直接调用customFetcher,不用传任何Context相关参数。
二、关于JWT的存储建议
accessJWT(短令牌)
- 优先存在React Context/全局状态(内存中):因为accessJWT有效期短,即使被窃取危害小,而且前端可以直接读取放到请求头。
- 如果需要跨域请求或服务端渲染,可以同时存到Cookie,但要设置
Secure(仅HTTPS传输)、SameSite=Strict/Lax(防CSRF),不要设置HttpOnly(否则前端拿不到,没法手动加到请求头)。
refreshJWT(长令牌)
- 必须存在HttpOnly Cookie:HttpOnly Cookie前端无法读取,只能由浏览器自动携带到后端的刷新接口,能有效避免XSS攻击窃取。同时要设置
Secure、SameSite=Strict,进一步提升安全性。 - 绝对不能存在localStorage/内存/普通Cookie,否则一旦前端被XSS攻击,refreshJWT被窃取后,攻击者可以长期冒充用户。
内容的提问来源于stack exchange,提问作者Billy Rogers
相关产品推荐
相关产品推荐

