如何创建使用 typesafe-actions AsyncCreator 的通用异步请求 epics 来简化 Redux 中 api 请求流的处理

How to create generic async request epics that uses typesafe-actions AsyncCreator to simplify handling api request flows in Redux

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

是否可以为给定的 typesafe-actions 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>;
}

已解决并成功编译。这是一个类型声明问题。

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

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

执行此操作并将我的史诗签名重构为后编译的代码:

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

我现在有一个从 api 中一般检索资源列表的史诗。其他 epics 可以跟随补丁,post,put 操作等