typescript - Hur man skapar generiska asynkrona request-epics som använder typesafe-actions AsyncCreator för att förenkla hantering av API-requestflöden i Redux


Jag är en nybörjare som har svårt att förstå den bästa arkitekturen för att koppla epics till asynkrona åtgärder i en Typescript Redux-miljö....

Är det möjligt att skapa en generell asynkron begäran epic för en given typesafe-actions AsyncCreator-instans?

Jag har provat koden nedan men förlorar typinformationen på action-payloaden... endast typen-egenskapen känns igen på den filtrerade action... Transpileringsfelet som jag får är...

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;
};

Har någon uppnått detta som ett alternativ till att skapa ett epik för varje individuell API-åtgärdstyp per resurs? Till exempel att skapa ett epik uttryckligen för varje flöde:

  1. [HÄMTA_ATT_GÖRA_FÖRFRÅGAN, HÄMTA_ATT_GÖRA_SUCCÉ, HÄMTA_ATT_GÖRA_MISSLYCKANDE],
  2. [POST_TODO_REQUEST, POST_TODO_SUCCESS, POST_TODO_FAILURE].......,
  3. [HÄMTA_INLÄGG_FÖRFRÅGAN, HÄMTA_INLÄGG_SUCCÈ, HÄMTA_INLÄGG_FAILL]
  4. [PUT_POST_REQUEST, PUT_POST_SUCCESS, PUT_POST_FAILURE]........, etc.

Uppdaterad information

Lite närmare.... ändrade metodens funktionssignatur för att acceptera datamodelltyp.

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;
};

Får nu ett annat fel:

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'.

Har det något att göra med att försöka matcha generisk åtgärd som används i episka med RootAction-typer i Epic-typsdeklarationen???

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>;
}

Svar #1

Löste det och lyckades få det att kompilera. Det var ett problem med typpåståenden.

När du använder Typescript måste de åtgärdstyper som deklareras i epicsignaturen vara en delmängd av alla åtgärdstyper som deklarerades när mellanprogrammet sattes upp.

Jag hade inte instansierat en instans av en ny generell åtgärd och jag hade inte heller lagt till den som en typ i RootAction (union av alla åtgärdstyper). RootAction innehöll fortfarande några gamla åtgärdstyper från den äldre arkitekturen innan omstruktureringen.

Koden kompilerades efter att ha gjort detta och omstrukturerat min episka signatur till:

  • Specificera delmängden av åtgärdstyper som accepteras som input till epiken
  • Ange delmängden av åtgärdstyper som genereras av episken

Jag har nu en epic för att generiskt hämta en lista med resurser från ett API. Andra epics kan följa för patch-, post-, put-operationer osv.