如何让React在Axios拦截器修改令牌后触发组件重渲染?
解决React useEffect因Axios拦截器修改localStorage不触发的问题
我需要让useEffect钩子在用户令牌变化时执行,它负责更新用户信息并切换认证视图/组件。但React不监听localStorage变化,Axios拦截器又不是React组件,无法直接调用setAccessToken等状态更新函数,导致useEffect无法触发。
方案一:使用自定义事件通知状态更新
步骤1:修改Axios拦截器,触发自定义事件
在拦截器修改localStorage后,通过window.dispatchEvent触发自定义事件,传递令牌变化信息:
instance.interceptors.response.use( (res) => { return res; }, async (err) => { const originalConfig = err.config; if (err?.response?.data?.code === "token_not_valid" && err?.response) { // 过期access token,刷新成功 if (err.response.status === 403 && !originalConfig._retry) { originalConfig._retry = true; try { const resp = await instance.post("/token/refresh/", { refresh: TokenService.getLocalRefreshToken(), }); const { access } = resp.data; TokenService.updateLocalAccessToken(access); // 触发令牌更新事件 window.dispatchEvent(new CustomEvent('tokenUpdated', { detail: { access } })); return instance(originalConfig); } catch (_error) { return Promise.reject(_error); } } // 过期refresh token,清除令牌 if (err.response.status === 401 && !originalConfig._retry) { localStorage.removeItem("access"); localStorage.removeItem("refresh"); // 触发令牌清除事件 window.dispatchEvent(new CustomEvent('tokenCleared')); } } return Promise.reject(err); }, );
步骤2:在AuthProvider中监听事件并更新状态
在AuthProvider组件中添加事件监听,收到事件后更新React状态,从而触发useEffect:
export const AuthProvider = ({ children }: { children: ReactNode }) => { const [accessToken, setAccessToken] = useState( TokenService.getLocalAccessToken(), ); const [refreshToken, setRefreshToken] = useState( TokenService.getLocalRefreshToken(), ); useEffect(() => { // 处理令牌更新 const handleTokenUpdate = (e: CustomEvent) => { setAccessToken(e.detail.access); }; // 处理令牌清除 const handleTokenCleared = () => { setAccessToken(null); setRefreshToken(null); }; // 添加事件监听 window.addEventListener('tokenUpdated', handleTokenUpdate as EventListener); window.addEventListener('tokenCleared', handleTokenCleared as EventListener); // 组件卸载时清理监听 return () => { window.removeEventListener('tokenUpdated', handleTokenUpdate as EventListener); window.removeEventListener('tokenCleared', handleTokenCleared as EventListener); }; }, []); useEffect(() => { async function fetchUser() { if (accessToken) { // 获取当前用户信息 } else { // 清除用户信息 } } fetchUser(); }, [accessToken, refreshToken]); // ...其他逻辑 };
方案二:给Axios实例注入状态更新回调
将React的状态更新函数通过回调的方式注入Axios拦截器,直接在拦截器中更新状态,无需依赖localStorage事件。
步骤1:改造Axios实例创建方法,支持回调参数
// 创建带回调的Axios实例 export const createAuthApiInstance = ( onTokenUpdate?: (access: string) => void, onTokenClear?: () => void ) => { const instance = axios.create({ // 你的基础配置,比如baseURL等 }); instance.interceptors.response.use( (res) => res, async (err) => { const originalConfig = err.config; if (err?.response?.data?.code === "token_not_valid" && err?.response) { if (err.response.status === 403 && !originalConfig._retry) { originalConfig._retry = true; try { const resp = await instance.post("/token/refresh/", { refresh: TokenService.getLocalRefreshToken(), }); const { access } = resp.data; TokenService.updateLocalAccessToken(access); // 调用更新回调 onTokenUpdate?.(access); return instance(originalConfig); } catch (_error) { return Promise.reject(_error); } } if (err.response.status === 401 && !originalConfig._retry) { localStorage.removeItem("access"); localStorage.removeItem("refresh"); // 调用清除回调 onTokenClear?.(); } } return Promise.reject(err); }, ); return instance; };
步骤2:在AuthProvider中创建实例并传入回调
export const AuthProvider = ({ children }: { children: ReactNode }) => { const [accessToken, setAccessToken] = useState( TokenService.getLocalAccessToken(), ); const [refreshToken, setRefreshToken] = useState( TokenService.getLocalRefreshToken(), ); // 创建带回调的Api实例,确保只初始化一次 const [apiInstance] = useState(() => createAuthApiInstance( (access) => setAccessToken(access), () => { setAccessToken(null); setRefreshToken(null); } )); // 将apiInstance通过context传递给子组件使用 const authContext = useMemo(() => ({ accessToken, refreshToken, apiInstance, // ...其他需要暴露的方法/状态 }), [accessToken, refreshToken, apiInstance]); useEffect(() => { async function fetchUser() { if (accessToken) { // 获取当前用户信息 } else { // 清除用户信息 } } fetchUser(); }, [accessToken, refreshToken]); return <AuthContext.Provider value={authContext}>{children}</AuthContext.Provider>; };
方案对比
- 自定义事件:无需大幅修改现有代码结构,耦合度低,但需要手动管理事件监听的生命周期,适合快速适配现有项目。
- 回调注入:状态更新逻辑更直接,将React状态作为唯一可信源,避免
localStorage与组件状态不一致的问题,适合新项目或重构场景。
内容的提问来源于stack exchange,提问作者user21169813
相关产品推荐
相关产品推荐

