#reactjs #typescript #redux-toolkit
Вопрос:
Я только что создал свое первое приложение с RTK. Все работало нормально, поэтому я подумал, что брошу вызов самому себе и конвертирую его в TypeScript. Преобразовав свой первый компонент, я избавился от всех ошибок, кроме одной. Я думаю, я изо всех сил пытаюсь понять, где определить тип.
В моем extraReducers
случае у меня есть два отклоненных обращения из двух асинхронных действий. Ошибка, которую я получаю, довольно проста, Object is of type 'unknown'
в моих payload
свойствах внутри отклоненных обращений. Но я не могу понять, где определить объект полезной нагрузки. Любая помощь приветствуется. Код ниже
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
import { RootState } from 'rootReducer';
import Axios from 'utils/axios';
interface ForgotPasswordState {
status: {
isPending: boolean;
isSuccess: boolean;
isError: boolean;
errorMessage: string;
},
}
export interface ForgotPasswordRequestProps {
email: string;
}
export interface ForgotPasswordSetProps {
query: string | null;
password: string
}
export const forgotPasswordRequest = createAsyncThunk(
'auth/forgotPasswordRequest',
async (
data: ForgotPasswordRequestProps,
{ rejectWithValue }) => {
const { email } = data;
try {
const response = await Axios.anon.post(`/v1/auth/forgot-password`, {
email,
});
if (response.status === 204) {
return null;
}
} catch (error) {
if (error instanceof Error) {
return rejectWithValue(error.message);
}
return console.log('ERROR', error);
}
}
);
export const forgotPasswordSet = createAsyncThunk(
'auth/verifyEmailSet',
async (
data: ForgotPasswordSetProps,
{ rejectWithValue }) => {
const { query, password } = data;
try {
const response = await Axios.anon.post(`/v1/auth/reset-password?token=${query}`, {
password,
});
if (response.status === 204) {
return null;
}
} catch (error) {
if (error instanceof Error) {
return rejectWithValue(error.message)
}
return console.log("ERROR", error)
}
}
);
const initialState: ForgotPasswordState = {
status: {
isPending: false,
isSuccess: false,
isError: false,
errorMessage: '',
},
}
export const forgotPasswordSlice = createSlice({
name: 'forgotPassword',
initialState,
reducers: {
clearState: (state) => {
state.status.isError = false;
state.status.isSuccess = false;
state.status.isPending = false;
state.status.errorMessage = '';
return state;
},
},
extraReducers: builder => {
builder
.addCase(
forgotPasswordRequest.fulfilled, (state) => {
state.status.isPending = false;
state.status.isSuccess = true;
})
.addCase(
forgotPasswordRequest.rejected, (state, action ) => {
const { payload } = action;
state.status.isPending = false;
state.status.isError = true;
// "Object is of type 'unknown'" on payload below
state.status.errorMessage = payload.message;
})
.addCase(
forgotPasswordRequest.pending, (state) => {
state.status.isPending = true;
})
.addCase(
forgotPasswordSet.fulfilled, (state) => {
state.status.isPending = false;
state.status.isSuccess = true;
})
.addCase(
forgotPasswordSet.rejected, (state, action ) => {
const { payload } = action;
state.status.isPending = false;
state.status.isError = true;
// "Object is of type 'unknown'" on payload below
state.status.errorMessage = payload.message;
})
.addCase(
forgotPasswordSet.pending, (state) => {
state.status.isPending = true;
})
}
});
export const { clearState } = forgotPasswordSlice.actions;
export const statusSelector = (state: RootState) => state.auth.forgotPassword.status;
Что я пробовал
Как и было предложено, я попытался установить в моем случае
(state, action: { payload: ErrorPayload }) => {...}
И создание (я думаю) соответствующего типа
export interface ErrorPayload {
message: string
}
но это просто приводит к
No overload matches this call.
Overload 1 of 2, '(actionCreator: AsyncThunkRejectedActionCreator<ForgotPasswordRequestProps, {}>, reducer: CaseReducer<ForgotPasswordState, PayloadAction<...>>): ActionReducerMapBuilder<...>', gave the following error.
Argument of type '(state: WritableDraft<ForgotPasswordState>, action: { payload: ErrorPayload; }) => void' is not assignable to parameter of type 'CaseReducer<ForgotPasswordState, PayloadAction<unknown, string, { arg: ForgotPasswordRequestProps; requestId: string; requestStatus: "rejected"; aborted: boolean; condition: boolean; } amp; ({ ...; } | ({ ...; } amp; {})), SerializedError>>'.
Types of parameters 'action' and 'action' are incompatible.
Type 'PayloadAction<unknown, string, { arg: ForgotPasswordRequestProps; requestId: string; requestStatus: "rejected"; aborted: boolean; condition: boolean; } amp; ({ rejectedWithValue: true; } | ({ ...; } amp; {})), SerializedError>' is not assignable to type '{ payload: ErrorPayload; }'.
Types of property 'payload' are incompatible.
Type 'unknown' is not assignable to type 'ErrorPayload'.
Overload 2 of 2, '(type: string, reducer: CaseReducer<ForgotPasswordState, Action<string>>): ActionReducerMapBuilder<ForgotPasswordState>', gave the following error.
Argument of type 'AsyncThunkRejectedActionCreator<ForgotPasswordRequestProps, {}>' is not assignable to parameter of type 'string'.
Также, как и предлагалось, я попытался установить для своего обращения значение
(state, action: PayloadAction<string>) => {...}
и импорт PayloadAction
из '@reduxjs/toolkit'
. Это также не сработало и привело к следующим ошибкам
No overload matches this call.
Overload 1 of 2, '(actionCreator: AsyncThunkRejectedActionCreator<ForgotPasswordRequestProps, {}>, reducer: CaseReducer<ForgotPasswordState, PayloadAction<...>>): ActionReducerMapBuilder<...>', gave the following error.
Argument of type '(state: WritableDraft<ForgotPasswordState>, action: { payload: string; type: string; }) => void' is not assignable to parameter of type 'CaseReducer<ForgotPasswordState, PayloadAction<unknown, string, { arg: ForgotPasswordRequestProps; requestId: string; requestStatus: "rejected"; aborted: boolean; condition: boolean; } amp; ({ ...; } | ({ ...; } amp; {})), SerializedError>>'.
Types of parameters 'action' and 'action' are incompatible.
Type 'PayloadAction<unknown, string, { arg: ForgotPasswordRequestProps; requestId: string; requestStatus: "rejected"; aborted: boolean; condition: boolean; } amp; ({ rejectedWithValue: true; } | ({ ...; } amp; {})), SerializedError>' is not assignable to type '{ payload: string; type: string; }'.
Types of property 'payload' are incompatible.
Type 'unknown' is not assignable to type 'string'.
Overload 2 of 2, '(type: string, reducer: CaseReducer<ForgotPasswordState, { payload: string; type: string; }>): ActionReducerMapBuilder<ForgotPasswordState>', gave the following error.
Argument of type 'AsyncThunkRejectedActionCreator<ForgotPasswordRequestProps, {}>' is not assignable to parameter of type 'string'.
Комментарии:
1. Вы можете просто добавить аннотацию типа
(state, action ) =>
, например:(state, action: { payload: PayloadType }) =>
2. @Cerbrus Я пробовал это, но, должно быть, я делаю что-то не так. Если я сделаю так, как вы предложили, и создам что-то вроде
export interface PayloadType { message: string }
, я получу...is not assignable to parameter of type...
сообщение об ошибке
Ответ №1:
В вашем действии измените его на
// Assuming that your expected action is only a message string
// If it's more than that, then change the PayloadAction type param to your expected interface
// You'll need to import PayloadAction from '@reduxjs/toolkit'
.addCasePasswordRequest.rejected, (state, action: PayloadAction<string>) => {
state.status.isPending = false
state.status.isError = true
state.status.errorMessage = action.payload
Кроме того, чтобы уменьшить объем данных, вы можете создать enum
enum RequestStatus {
Pending = 'PENDING',
Error = 'ERROR',
Success = 'SUCCESS',
}
interface ForgotPasswordState {
status: RequestStatus
errorMessage: string
}
Затем в ваших различных редукторах вместо установки isPending
, isError
, и isSuccess
и вызова их по отдельности, вы можете просто установить его как
state.status = RequestStatus.Error
state.errorMessage = action.payload
Ваш селектор не нужно менять, но все, что подписано на фрагмент, должно было бы изменить логику с
if (forgotPasswordStatus.isError) {...}
// Change to
if (forgotPasswordStatus.status === RequestStatus.Error) {...}
Комментарии:
1. спасибо за ответ. К сожалению, это тоже не сработало. Я обновил свой вопрос ответом об ошибке
Ответ №2:
Переход к использованию с TypeScript docs для createAsyncThunk
const forgotPasswordRequest = createAsyncThunk<
// Return type of the payload creator
unknown,
// First argument to the payload creator
ForgotPasswordSetProps,
{
// Optional fields for defining thunkApi field types
rejectValue: { message: string }
}
>('auth/verifyEmailSet',
async (
data,
{ rejectWithValue }) => {
const { query, password } = data;
try {
const response = await Axios.anon.post(`/v1/auth/reset-password?token=${query}`, {
password,
});
if (response.status === 204) {
return null;
}
} catch (error) {
if (error instanceof Error) {
return rejectWithValue(error.message)
}
return console.log("ERROR", error)
}
}
);
Указание этого общего там позаботится об этом везде.
Ответ №3:
После некоторых исследований я обнаружил этот поток, который все очищает.
https://github.com/reduxjs/redux-toolkit/issues/766#issuecomment-711415387
Дело в том, что, хотя вы точно знаете, что если есть полезная нагрузка, она имеет тип LoginError, есть еще два сценария, в которых вы можете столкнуться с этой отклоненной ситуацией:
- вы отклоняете его с помощью rejectWithValue — в этом случае устанавливается полезная нагрузка.
- выдается ошибка — в этом случае полезная нагрузка не установлена. Это может произойти, если вы повторно создаете ошибку, или ошибка возникает в вашем блоке catch или за его пределами. Поскольку мы не можем точно знать, может ли такая ситуация возникнуть в вашем коде, мы должны предположить, что это возможно, и поэтому полезная нагрузка необязательна.