#reactjs #redux #redux-toolkit #rtk-query
#reactjs #сокращение #redux-инструментарий #rtk-запрос
Вопрос:
Пытаюсь найти наилучший способ добиться следующего:
- API через
createApi
- Фрагмент аутентификации через
createSlice
- Очистите состояние аутентификации при повторном использовании, когда API получит 401.
Нет проблем с первыми 2! Но проблема заключается в том, что мне нужно добавить перехватчик в API (недопустимая аутентификация — очистить локальное состояние аутентификации):
// api.ts
import authSlice from './authSlice';
const baseQueryWithReauth: BaseQueryFn<string | FetchArgs, unknown, FetchBaseQueryError> = async (
args,
api,
extraOptions
) => {
const result = await baseQuery(args, api, extraOptions)
if (result.error?.status === 401) {
api.dispatch(authSlice.actions.clearAuth())
}
return result
}
export const api = createApi({
reducerPath: API_REDUCER_KEY,
baseQuery: baseQueryWithReauth,
endpoints: () => ({}),
})
// authService.ts
import { User } from '../../models/User'
import { api } from '../api'
export const API_REDUCER_KEY = 'api'
interface AuthResponse {
user: User
jwt: string
}
interface LoginData {
email: string
password: string
}
export const authApi = api.injectEndpoints({
endpoints: (builder) => ({
login: builder.mutation<AuthResponse, LoginData>({
query: (body) => {
return {
url: 'login',
method: 'POST',
body,
}
},
}),
}),
})
export const { useLoginMutation } = authApi
Это вызывает циклическую зависимость — поскольку фрагменту аутентификации необходимо вызвать API для функций аутентификации (вход, выход и т. Д.).:
// authSlice.ts
import { api } from './api';
...
export const loginLocal = createAsyncThunk<
Pick<AuthState, 'user' | 'accessToken'>,
{
email: string
password: string
}
>('auth/login', async ({ email, password }, { dispatch }) => {
const response = await dispatch(
authApi.endpoints.login.initiate({
email,
password,
})
)
if ('error' in response) {
if ('status' in response.error) {
throw new Error(response.error.data as string)
}
throw new Error(response.error.message)
}
const { data } = response
const { jwt: accessToken, user } = data
return { user, accessToken }
})
Циклическая зависимость выглядит следующим образом: authSlice -> AuthService -> api -> authSlice
Есть ли способ обойти это — или есть лучший / другой шаблон, который я могу использовать?
Комментарии:
1. разве это не решит проблему, если вы перейдете
baseQueryWithReauth
в отдельный файл? чем вам не нужно импортироватьauthSlice
в свойapi.ts
2. Кроме того, почему
loginLocal
записывается как acreateAsyncThunk
? Это похоже на то, что также вошло бы в фрагмент API.3. @TheWuif — я считаю, что это все равно будет циклическая зависимость, только с дополнительным звеном в цепочке: authSlice -> AuthService -> api -> baseQueryWithReauth -> authSlice
4. @markerikson — Я также применяю некоторую асинхронную логику в этой функции, которая не имеет отношения к вопросу 🙂
Ответ №1:
Простым решением было просто перенести действие «clearAuth» в его собственный файл — согласно https://redux-toolkit.js.org/usage/usage-guide#exporting-and-using-slices:
Обычно для этого требуется извлечь общий код в отдельный общий файл, который оба модуля могут импортировать и использовать. В этом случае вы можете определить некоторые общие типы действий в отдельном файле с помощью createAction, импортировать создателей этих действий в каждый файл среза и обрабатывать их с помощью аргумента extraReducers .
// clearAuthAction.ts
import { createAction } from '@reduxjs/toolkit'
export const CLEAR_AUTH = 'auth/clearAuth'
export const clearAuth = createAction<void>(CLEAR_AUTH)
Затем используйте конструктор в authSlice:
// authSlice.ts
builder.addCase(CLEAR_AUTH, (state) => {
state.user = null
state.accessToken = null
})
И отправка clearAuth в API:
// api.ts
const baseQueryWithReauth: BaseQueryFn<string | FetchArgs, unknown, FetchBaseQueryError> = async (
args,
api,
extraOptions
) => {
const result = await baseQuery(args, api, extraOptions)
if (result.error?.status === 401 || result.error?.status === 403) {
api.dispatch(clearAuth())
}
return result
}