如何在RTK Query中实现Refresh Token刷新功能?
在RTK Query中实现Token自动刷新的正确方式
核心问题分析
你遇到的报错是因为React Hook只能在组件或自定义Hook中调用,不能在普通工具函数里使用useLazyGetRefreshTokensQuery。正确的做法是利用RTK Query的自定义baseQuery逻辑,通过非Hook方式触发token刷新请求,统一处理所有API的token验证与刷新。
方案一:捕获401错误后自动刷新并重试请求
这种方式无需提前判断token过期,等后端返回401(token失效)时再触发刷新,实现简单。
1. 定义刷新Token的API端点
先创建用于刷新token的独立API(如果还没实现):
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; export const authApi = createApi({ reducerPath: 'authApi', baseQuery: fetchBaseQuery({ baseUrl: '/api/auth' }), endpoints: builder => ({ refreshToken: builder.mutation({ query: refreshToken => ({ url: '/refresh', method: 'POST', body: { refreshToken } }) }) }) }); // 组件中可以用这个Hook,但我们在baseQuery里用非Hook方式调用 export const { useRefreshTokenMutation } = authApi;
2. 封装带刷新逻辑的自定义baseQuery
扩展fetchBaseQuery,加入401错误拦截与token刷新逻辑:
import { fetchBaseQuery, BaseQueryFn } from '@reduxjs/toolkit/query/react'; import { RootState } from '../store'; // 导入你的Redux Store根类型 import { authApi } from './authApi'; // 假设你有这些工具函数:获取/存储token、刷新token import { getAccessToken, getRefreshToken, setAccessToken, clearAuthData } from '../util/userContextManagement'; // 基础查询逻辑 const baseQuery = fetchBaseQuery({ baseUrl: '/api/users/current', prepareHeaders: (headers) => { const token = getAccessToken()?.token; if (token) { headers.set('Authorization', `Bearer ${token}`); } return headers; } }); // 带token刷新的增强版baseQuery const baseQueryWithReauth: BaseQueryFn = async (args, api, extraOptions) => { let result = await baseQuery(args, api, extraOptions); // 检测是否为token过期导致的401错误 if (result.error && result.error.status === 401) { const refreshToken = getRefreshToken(); if (refreshToken) { // 用dispatch触发刷新请求(非Hook方式,避免报错) const refreshResult = await api.dispatch( authApi.endpoints.refreshToken.initiate(refreshToken) ); if (refreshResult.data) { // 保存新的access token setAccessToken(refreshResult.data); // 重试原请求 result = await baseQuery(args, api, extraOptions); } else { // 刷新失败,清除本地授权数据并跳转登录页 clearAuthData(); window.location.href = '/login'; } } else { // 无刷新token,直接跳转登录 clearAuthData(); window.location.href = '/login'; } } return result; };
3. 在业务API中使用增强版baseQuery
修改你的contactsApi,替换原有的baseQuery:
import { createApi } from '@reduxjs/toolkit/query/react'; import { Contacts } from '../datamodel/Contact'; import { baseQueryWithReauth } from './baseQueryWithReauth'; export const contactsApi = createApi({ reducerPath: 'contactsApi', baseQuery: baseQueryWithReauth, // 使用带刷新逻辑的baseQuery endpoints: builder => ({ getContacts: builder.query<Contacts, void>({ query: () => '/contacts' // GET请求可简化写法 }) }) }); export const { useGetContactsQuery } = contactsApi;
方案二:提前检测Token过期,主动刷新
如果你的token包含过期时间,可以在请求发起前主动检查并刷新,避免401错误,体验更流畅。
修改prepareHeaders为异步函数,提前处理token刷新:
const baseQuery = fetchBaseQuery({ baseUrl: '/api/users/current', prepareHeaders: async (headers, { api }) => { let tokenData = getAccessToken(); let token = tokenData?.token; const expiresAt = tokenData?.expiresAt; // 提前1分钟刷新即将过期的token if (token && expiresAt && Date.now() >= expiresAt - 60000) { const refreshToken = getRefreshToken(); if (refreshToken) { const refreshResult = await api.dispatch( authApi.endpoints.refreshToken.initiate(refreshToken) ); if (refreshResult.data) { token = refreshResult.data.accessToken; setAccessToken(refreshResult.data); } else { clearAuthData(); token = null; } } } if (token) { headers.set('Authorization', `Bearer ${token}`); } return headers; } });
关键注意事项
- 不要在非Hook函数中调用RTK Query的Hook,改用
api.dispatch(api.endpoints.xxx.initiate(...))触发请求,这是官方支持的非Hook调用方式。 - 刷新token后必须更新本地存储的token,确保后续请求使用新token。
- 刷新失败时要及时清除本地授权数据并跳转登录页,避免无限循环请求。
内容的提问来源于stack exchange,提问作者CodeGrinder
相关产品推荐
相关产品推荐

