在State片段中使用异步函数

因为在Redux中,所有的Action都必须是同步的,所以不能直接在Reducer或者State片段中定义返回Promise的异步Action。在传统Redux中,对于异步数据的支持也是通过redux-thunk或者redux-promise等中间件实现的。

其实要将异步过程加入到Redux的Action处理流程中,只需要将异步Action拆分成两个处理步骤即可,即启动异步Action的调用和处理Action的返回值。这样就可以将一个异步Action转换为两个同步的Action。

为了简化这个处理的过程,Redux Toolkit提供了一个名为createAsyncThunk的函数来构建便于State片段处理的异步Action。

createAsyncThunk函数可以接受三个参数来创建异步Action,以下是其函数签名。

// 用于向异步Action提供可供使用的额外功能与属性
// 从这个类型定义可以看出,异步处理函数中可以使用state、extra、dispatch函数等内容。
// 例如调用getState()可以获取state,调用dispatch()可以调用dispatch函数,
// 调用fulfillWithValue(value, meta)可以代替return从异步Action中返回处理成功的值,
// 调用rejectWithValue(value, meta)可以代替return或者thorw从异步Action中返回处理失败的值。
type GetThunkAPI<ThunkApiConfig> = BaseThunkAPI<
  GetState<ThunkApiConfig>,
  GetExtra<ThunkApiConfig>,
  GetDispatch<ThunkApiConfig>,
  GetRejectValue<ThunkApiConfig>,
  GetRejectedMeta<ThunkApiConfig>,
  GetFulfilledMeta<ThunkApiConfig>
>;

// 此类型定义了异步Action实际上是如何工作的,最终确认或者拒绝什么结果。这个类型在实际应用中实际上是一个返回了Promise类型值的异步函数。
// 这个异步函数只能接受一个参数,所以如果想要向函数传递多个参数,可以使用对象来组织。
type AsyncThunkPayloadCreator<Returned, ThunkArg = void, ThunkApiConfig extends AsyncThunkConfig = {}> = (
  arg: ThunkArg,
  thunkAPI: GetThunkAPI<ThunkApiConfig>
) => AsyncThunkPayloadCreatorReturnValue<Returned, ThunkApiConfig>;

// createAsyncThunk定义了一个异步Action本身,其第一个参数type用于定义这个action的基础action type。
// createAsyncThunk会使用给定的action type作为前缀创建三个用于第二步action处理的action type,分别是:
// pending,fulfilled,rejected,分别代表执行中、执行成功和执行失败。
function createAsyncThunk<Returned, ThunkArg = void>(
  typePrefix: string,
  payloadCreator: AsyncThunkPayloadCreator<Returned, ThunkArg, {}>,
  options?: AsyncThunkOptions<ThunkArg, {}>
): AsyncThunk<Returned, ThunkArg, {}>;

这个函数签名看起来十分的复杂,但是createAsyncThunk在使用的时候并不复杂。createAsyncThunk定义的异步Action是以独立Action出现的,并不需要定义在State片段内部,但是在State片段内部还是需要对createAsyncThunk创建的异步Action产生的结果Action进行处理。所需要处理的结果Action有以下几个。

  • .pending,会根据createAsyncThunk中提供的Action Type名称自动创建一个附加了/pending的新Action Type,用于表示异步过程正在执行过程中。
  • .fulfilled,同样会自动创建一个附加了/fulfilled的新Action Type,用于表示异步过程已经成功执行,并返回了成功执行的结果。
  • .rejected,会自动创建一个附加了/rejected的新Action Type,用于表示异步过程出现错误,并返回了执行错误的信息。

对于异步Action的处理不能直接在State片段的reducers属性中定义,只能在extraReducers中定义。以下是延续上一节中的示例,并在其中增加了异步请求的Action。

import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';

interface CounterState {
  value: number;
}

// 这个Action可以在应用中使用 dispatch(increaseByRemoteAmount(10)) 的形式来触发。
export const increaseByRemoteAmount = createAsyncThunk(
  'counter/increateByRemoteAmount',
  async (amount: number, thunkAPI) => {
    const response = await fetch('/api/increase');
    // 这里可以使用thunkAPI参数中提供的fulfillWithValue()方法,也可以直接使用return返回值。
    thunkAPI.fulfillWithValue(response.amount);
  }
);

const counterSlice = createSlice({
  name: 'counter',
  initialState: { value: 0 },
  reducers: {
    // 这里延续上一节中定义的Reducer。
  },
  extraeReducers: builder => {
    // builder中需要响应处理的是异步Action的fulfilled、rejected和pending这几个属性,
    // 它们实际上就代表了异步Action在完成处理以后触发的Action的Action Type。
    builder.addCase(increaseByRemoteAmount.fulfilled, (state, action) => {
      // 从异步Action中返回的值,会保存在action的payload属性中传入。
      state.value += action.payload;
    });
  }
});

Tip

Redux Toolkit还提供了一套用于执行异步请求的RTK Query功能库,这个异步请求库可以提供客户端的数据缓存和数据访问控制,并且可以很好的通过Redux Toolkit与Redux结合使用。如果在应用中使用了Redux Toolkit,可以优先尝试使用RTK Query作为异步数据访问库。

从技术选型角度说,RTK Query与后续章节中介绍的React Query的功能比较类似,基本上可以在使用Redux的应用中替代React Query使用。但是RTK Query的细节功能和对于异步数据访问控制的精细度不如React Query,这需要根据自身应用的需求甄别使用。