如何在 Typescript 中使用 createAsyncThunk?如何设置`pending`和`rejected`有效载荷的类型?

时间:2021-04-23 09:02:17

标签: typescript redux redux-thunk redux-toolkit

现在我已经有了这些用于上传 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 异步处理程序的返回是 payload 操作的 fulfilled,对吗?

但是如何为 payloadpending 操作设置 rejected 类型?我应该向 try-catch 处理程序添加 createAsyncThunk 块吗?

这是我应该做的相关性吗?

  • pending === "UPLOAD_START"
  • fulfilled === "UPLOAD_SUCCESS"
  • rejected === "UPLOAD_FAILURE"

Obs:从我想象的模式来看,我编写的代码不会比我已经用三个单独的动作做的更少,并在我的常规中处理它们减速器(而不是在 extraReducers 道具上这样做)。在这种情况下使用 createAsyncThunk 有什么意义?

1 个答案:

答案 0 :(得分:3)

通过查看您链接的文档页面中稍远一点的 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 的 returned 值将是“已完成”操作的 payload
  • 如果您return rejectWithResult(value),那将成为“拒绝”操作的payload
  • 如果您只是 throw,那将成为“拒绝”操作的 error

其他答案:

  • “pending”是您的“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 触发重新渲染(它最有可能触发)并且在重新渲染期间的某个地方,错误为 thrown,该错误将在此 try..catch 块和另一个 {{1 }} 将被派遣。因此,您将同时拥有成功和错误情况的 thunk。尴尬的。这可以通过将结果存储在一个作用域范围内的变量中并在 try-catch-block 之外分派来规避,但实际上谁这样做呢? ;) errorAction 为您照顾的这些小事,让我觉得值得。