首页 > 解决方案 > 如何创建使用 typesafe-actions AsyncCreator 的通用异步请求史诗来简化在 Redux 中处理 api 请求流

问题描述

我是一个新手,正在努力理解将史诗与 Typescript redux 环境中的异步操作相关联的最佳架构......

是否可以为给定的类型安全操作 AsyncCreator 实例创建通用异步请求史诗?

我已经尝试使用下面的代码,但丢失了关于操作有效负载的类型信息......只有类型属性在过滤的动作中被识别......我收到的转译错误是......

Property 'payload' does not exist on type 'ReturnType<[TRequestPayload] extends [undefined] ? unknown extends TRequestPayload ? PayloadAC<TRequestType, TRequestPayload> : EmptyAC<TRequestType> : PayloadAC<TRequestType, TRequestPayload>>'.
const createAsyncEpic = <
  TModel, // generic type for data payload
  TRequestType extends string,
  TRequestPayload extends ApiRequest<TModel>,
  TSuccessType extends string,
  TSuccessPayload extends ApiSuccess<TModel>,
  TFailureType extends string,
  TFailurePayload extends ApiFailure<TModel>
>(
  asyncAction: AsyncActionCreator<
    [TRequestType, TRequestPayload],
    [TSuccessType, TSuccessPayload],
    [TFailureType, TFailurePayload]
  >,
) => {
  const epic: Epic<RootAction, RootAction, RootState, Services> = (
    action$,
    state$,
    { apiService },
  ) =>
    action$.pipe(
      filter(isActionOf(asyncAction.request)),
      switchMap(action =>
        apiService
          .api()
          .all<TModel>(action.payload.url) // property payload unrecognised, only type property recognised here
          .pipe(
            map(response => asyncAction.success(response.data)),
            catchError(error =>
              of(
                asyncAction.failure(error),
              ),
            ),
          ),
      ),
    );
  return epic;
};

有没有人实现了这一点,而不是为每个资源的每个单独的 api 操作类型创建史诗?例如,为每个流程显式创建史诗:

  1. [FETCH_TODO_REQUEST,FETCH_TODO_SUCCESS,FETCH_TODO_FAILURE],
  2. [POST_TODO_REQUEST, POST_TODO_SUCCESS, POST_TODO_FAILURE].......,
  3. [FETCH_POST_REQUEST,FETCH_POST_SUCCESS,FETCH_POST_FAILURE],
  4. [PUT_POST_REQUEST、PUT_POST_SUCCESS、PUT_POST_FAILURE]........等等。

更新信息

稍微接近.... 修改方法函数签名以接受数据模型类型。

const createAsyncEpic = <
  TModel // generic type for data payload model
>(
  asyncAction: AsyncActionCreator<
    [string, ApiRequest<TModel>],
    [string, ApiSuccess<TModel | TModel[]>],
    [string, ApiFailure<TModel>]
  >,
) => {
  const epic: Epic<RootAction, RootAction, RootState, Services> = (
    action$,
    state$,
    { courseServices, apiService },
  ) =>
    action$.pipe(
      filter(isActionOf(asyncAction.request)),
      switchMap(action =>
        apiService
          .api()
          .all<TModel>(action.payload.url)
          .pipe(
            map(resp =>
              asyncAction.success({
                request: action.payload, // Api request struct
                response: resp, // Axios Response
              }),
            ),
            catchError(error =>
              of(asyncAction.failure(action.payload.fail(error))),
            ),
          ),
      ),
    );
  return epic;
};

现在得到一个不同的错误:

ERROR in <path>/epics.ts
[tsl] ERROR in <path>/epics.ts(42,5)
      TS2322: Type 'Observable<PayloadAction<TSuccessType, ApiSuccess<TModel | TModel[]>>>' is not assignable to type 'Observable<PayloadAction<actions.FETCH_COURSES_REQUEST, RequestingComponent> | PayloadAction<actions.FETCH_COURSES_SUCCESS, Course[]> | PayloadAction<constants.NOTIFY_ERROR, ErrorReport> | PayloadAction<string, ApiSuccess<Course>> | { ...; } | { ...; } | PayloadAction<...> | PayloadAction<...>>'.
  Type 'PayloadAction<TSuccessType, ApiSuccess<TModel | TModel[]>>' is not assignable to type 'PayloadAction<actions.FETCH_COURSES_REQUEST, RequestingComponent> | PayloadAction<actions.FETCH_COURSES_SUCCESS, Course[]> | PayloadAction<constants.NOTIFY_ERROR, ErrorReport> | PayloadAction<string, ApiSuccess<Course>> | { ...; } | { ...; } | PayloadAction<...> | PayloadAction<...>'.
    Type 'PayloadAction<TSuccessType, ApiSuccess<TModel | TModel[]>>' is not assignable to type 'PayloadAction<actions.FETCH_COURSES_REQUEST, RequestingComponent>'.
      Type 'TSuccessType' is not assignable to type 'actions.FETCH_COURSES_REQUEST'.
        Type 'string' is not assignable to type 'actions.FETCH_COURSES_REQUEST'.

是否与将史诗中使用的通用动作与史诗类型声明中的 RootAction 类型相匹配?

export declare interface Epic<Input extends Action = any, Output extends Input = Input, State = any, Dependencies = any> {
  (action$: ActionsObservable<Input>, state$: StateObservable<State>, dependencies: Dependencies): Observable<Output>;
}

标签: typescriptreduxredux-observabletypesafe-actions

解决方案


解决了它并设法让它编译。这是一个类型声明问题。

使用 Typescript 时,史诗签名中声明的动作类型必须是设置中间件时声明的所有动作类型的子集。

我没有实例化新的通用动作的实例,也没有将它作为类型添加到 RootAction(所有动作类型的联合)。在重构之前,RootAction 还包含一些来自旧架构的旧动作类型。

执行此操作并将我的史诗签名重构为:

  • 指定作为史诗输入接受的动作类型的子集
  • 指定史诗输出的动作类型子集

我现在有一个史诗般的从 api 检索资源列表的史诗。其他史诗可以跟随补丁、发布、放置操作等。


推荐阅读