You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 08:27:53