如何在 Typescript 中使用 createAsyncThunk?如何为 `pending` 和 `rejected` payloads 设置类型?
How to use createAsyncThunk with Typescript? How to set types for the `pending` and `rejected` payloads?
现在我已经有了这些用于上传 thunk 生命周期的操作。
type UPLOAD_START = PayloadAction<void>
type UPLOAD_SUCCESS = PayloadAction<{ src: string, sizeKb: number }>
type UPLOAD_FAILURE = PayloadAction<{ error: string }>
我想将其转换为 createAsyncThunk
调用,假设它会减少代码。但是会吗?
从 https://redux-toolkit.js.org/api/createAsyncThunk 上的示例来看,它应该是这样的:
const uploadThumbnail = createAsyncThunk(
'mySlice/uploadThumbnail',
async (file: File, thunkAPI) => {
const response = await uploadAPI.upload(file) as API_RESPONSE
return response.data // IS THIS THE payload FOR THE fulfilled ACTION ?
}
)
这就是我处理生命周期操作的方式?
const usersSlice = createSlice({
name: 'mySlice',
initialState: // SOME INITIAL STATE,
reducers: {
// standard reducer logic, with auto-generated action types per reducer
},
extraReducers: {
// Add reducers for additional action types here, and handle loading state as needed
[uploadThumbnail.pending]: (state,action) => {
// HANDLE MY UPLOAD_START ACTION
},
[uploadThumbnail.fulfilled]: (state, action) => {
// HANDLE MY UPLOAD_SUCCESS ACTION
},
[uploadThumbnail.rejected]: (state, action) => {
// HANDLE MY UPLOAD_FAILURE ACTION
},
}
})
问题
我假设 createAsyncThunk
异步处理程序的 return 是 fulfilled
操作的 payload
,对吗?
但是如何为 pending
和 rejected
操作设置 payload
类型?我应该向 createAsyncThunk
处理程序添加一个 try-catch
块吗?
这是我应该做的关联吗?
pending === "UPLOAD_START"
fulfilled === "UPLOAD_SUCCESS"
rejected === "UPLOAD_FAILURE"
Obs: 从我想象的模式来看,我写的代码不会比我已经用三个单独的动作和在我的常规 reducer 中处理它们(而不是在 extraReducers
prop 上处理)。在这种情况下使用 createAsyncThunk
有什么意义?
您的大部分问题将通过查看您链接的文档页面中的一个 TypeScript 示例得到解答:
export const updateUser = createAsyncThunk<
User,
{ id: string } & Partial<User>,
{
rejectValue: ValidationErrors
}
>('users/update', async (userData, { rejectWithValue }) => {
try {
const { id, ...fields } = userData
const response = await userAPI.updateById<UpdateUserResponse>(id, fields)
return response.data.user
} catch (err) {
let error: AxiosError<ValidationErrors> = err // cast the error for access
if (!error.response) {
throw err
}
// We got validation errors, let's return those so we can reference in our component and set form errors
return rejectWithValue(error.response.data)
}
})
const usersSlice = createSlice({
name: 'users',
initialState,
reducers: {},
extraReducers: (builder) => {
// The `builder` callback form is used here because it provides correctly typed reducers from the action creators
builder.addCase(updateUser.fulfilled, (state, { payload }) => {
state.entities[payload.id] = payload
})
builder.addCase(updateUser.rejected, (state, action) => {
if (action.payload) {
// Being that we passed in ValidationErrors to rejectType in `createAsyncThunk`, the payload will be available here.
state.error = action.payload.errorMessage
} else {
state.error = action.error.message
}
})
},
})
所以,从那里的观察:
- 使用 TypeScript 时,您应该对 extraReducers 使用
builder
样式符号,并且您的所有类型都会自动为您推断。您不需要在 extraReducers
中手动输入任何内容 - 永远。
- 你的 thunk 的
return
ed 值将是“已完成”操作的 payload
- 如果你
return rejectWithResult(value)
,那将成为“被拒绝”行动的payload
- 如果你只是
throw
,那将成为“被拒绝”动作的error
。
其他答案:
- “待处理”是您的“UPLOAD_START”。它没有有效负载,您无法设置它。
不过,所有三个“待定”/“拒绝”/“已完成”都将具有
action.meta.arg
,这是您传递给 thunk 调用的原始值。
- 最后,这可能比您手写的代码要少一些,而且它在您的整个应用程序中都非常一致。此外,它还捕获了一些否则不会被发现的错误。
你知道吗
const manualThunk = async (arg) => {
dispatch(pendingAction())
try {
const result = await foo(arg)
dispatch(successAction(result))
} catch (e) {
dispatch(errorAction(e))
}
}
实际上包含错误?
如果 successAction
触发重新渲染(它很可能会触发)并且在重新渲染期间的某处,错误是 throw
n,该错误将在这个 try..catch
块和另一个 errorAction
将被派遣。因此,您将同时看到成功和错误情况都为真。尴尬的。这可以通过将结果存储在一个限定范围的变量中并在 try-catch-block 之外调度来规避,但实际上谁会这样做呢? ;)
createAsyncThunk
为您照顾的这些小事在我看来是值得的。
类型 'unknown' 不可分配给类型 'AxiosError
现在我已经有了这些用于上传 thunk 生命周期的操作。
type UPLOAD_START = PayloadAction<void>
type UPLOAD_SUCCESS = PayloadAction<{ src: string, sizeKb: number }>
type UPLOAD_FAILURE = PayloadAction<{ error: string }>
我想将其转换为 createAsyncThunk
调用,假设它会减少代码。但是会吗?
从 https://redux-toolkit.js.org/api/createAsyncThunk 上的示例来看,它应该是这样的:
const uploadThumbnail = createAsyncThunk(
'mySlice/uploadThumbnail',
async (file: File, thunkAPI) => {
const response = await uploadAPI.upload(file) as API_RESPONSE
return response.data // IS THIS THE payload FOR THE fulfilled ACTION ?
}
)
这就是我处理生命周期操作的方式?
const usersSlice = createSlice({
name: 'mySlice',
initialState: // SOME INITIAL STATE,
reducers: {
// standard reducer logic, with auto-generated action types per reducer
},
extraReducers: {
// Add reducers for additional action types here, and handle loading state as needed
[uploadThumbnail.pending]: (state,action) => {
// HANDLE MY UPLOAD_START ACTION
},
[uploadThumbnail.fulfilled]: (state, action) => {
// HANDLE MY UPLOAD_SUCCESS ACTION
},
[uploadThumbnail.rejected]: (state, action) => {
// HANDLE MY UPLOAD_FAILURE ACTION
},
}
})
问题
我假设 createAsyncThunk
异步处理程序的 return 是 fulfilled
操作的 payload
,对吗?
但是如何为 pending
和 rejected
操作设置 payload
类型?我应该向 createAsyncThunk
处理程序添加一个 try-catch
块吗?
这是我应该做的关联吗?
pending === "UPLOAD_START"
fulfilled === "UPLOAD_SUCCESS"
rejected === "UPLOAD_FAILURE"
Obs: 从我想象的模式来看,我写的代码不会比我已经用三个单独的动作和在我的常规 reducer 中处理它们(而不是在 extraReducers
prop 上处理)。在这种情况下使用 createAsyncThunk
有什么意义?
您的大部分问题将通过查看您链接的文档页面中的一个 TypeScript 示例得到解答:
export const updateUser = createAsyncThunk<
User,
{ id: string } & Partial<User>,
{
rejectValue: ValidationErrors
}
>('users/update', async (userData, { rejectWithValue }) => {
try {
const { id, ...fields } = userData
const response = await userAPI.updateById<UpdateUserResponse>(id, fields)
return response.data.user
} catch (err) {
let error: AxiosError<ValidationErrors> = err // cast the error for access
if (!error.response) {
throw err
}
// We got validation errors, let's return those so we can reference in our component and set form errors
return rejectWithValue(error.response.data)
}
})
const usersSlice = createSlice({
name: 'users',
initialState,
reducers: {},
extraReducers: (builder) => {
// The `builder` callback form is used here because it provides correctly typed reducers from the action creators
builder.addCase(updateUser.fulfilled, (state, { payload }) => {
state.entities[payload.id] = payload
})
builder.addCase(updateUser.rejected, (state, action) => {
if (action.payload) {
// Being that we passed in ValidationErrors to rejectType in `createAsyncThunk`, the payload will be available here.
state.error = action.payload.errorMessage
} else {
state.error = action.error.message
}
})
},
})
所以,从那里的观察:
- 使用 TypeScript 时,您应该对 extraReducers 使用
builder
样式符号,并且您的所有类型都会自动为您推断。您不需要在extraReducers
中手动输入任何内容 - 永远。 - 你的 thunk 的
return
ed 值将是“已完成”操作的payload
- 如果你
return rejectWithResult(value)
,那将成为“被拒绝”行动的payload
- 如果你只是
throw
,那将成为“被拒绝”动作的error
。
其他答案:
- “待处理”是您的“UPLOAD_START”。它没有有效负载,您无法设置它。
不过,所有三个“待定”/“拒绝”/“已完成”都将具有
action.meta.arg
,这是您传递给 thunk 调用的原始值。 - 最后,这可能比您手写的代码要少一些,而且它在您的整个应用程序中都非常一致。此外,它还捕获了一些否则不会被发现的错误。 你知道吗
const manualThunk = async (arg) => {
dispatch(pendingAction())
try {
const result = await foo(arg)
dispatch(successAction(result))
} catch (e) {
dispatch(errorAction(e))
}
}
实际上包含错误?
如果 successAction
触发重新渲染(它很可能会触发)并且在重新渲染期间的某处,错误是 throw
n,该错误将在这个 try..catch
块和另一个 errorAction
将被派遣。因此,您将同时看到成功和错误情况都为真。尴尬的。这可以通过将结果存储在一个限定范围的变量中并在 try-catch-block 之外调度来规避,但实际上谁会这样做呢? ;)
createAsyncThunk
为您照顾的这些小事在我看来是值得的。
类型 'unknown' 不可分配给类型 'AxiosError