如何使用 TypeScript 为 Redux-Observable 的 Epic 输入 Actions

Posted

技术标签:

【中文标题】如何使用 TypeScript 为 Redux-Observable 的 Epic 输入 Actions【英文标题】:How to type Actions for Redux-Observable's Epics with TypeScript 【发布时间】:2021-01-09 23:47:28 【问题描述】:

我在我的应用程序中设置 TypeScript 和 Redux-Observable,并有这个简单的流程,我想在这里使用 Epic:

dispatch('someAction')
fetch('relevant-endpoint-from-api')
getResponse(dispatch('someActionSuccess'))
failed(dispatch('someActionFailed'))

所以跟随 this article 就是这样做的,我试了一下。

这是我的 actions.ts 文件:

export interface MediasOrigin 
  id: number;
  label: string;
  url: string;
  zonesId: number[];
  scenariosRef: number[];
  stepsRef: number[];
  gearsId: number[];
  texturesId: number[];


export enum MediasActionTypes 
  MEDIAS_ORIGINS_FETCH_ALL = "MEDIAS_ORIGINS_FETCH_ALL",
  MEDIAS_ORIGINS_FETCH_ALL_SUCCESS = "MEDIAS_ORIGINS_FETCH_ALL_SUCCESS",
  MEDIAS_ORIGINS_FETCH_ALL_FAILED = "MEDIAS_ORIGINS_FETCH_ALL_FAILED"


export interface IMediasOriginsFetchAllAction 
  type: MediasActionTypes.MEDIAS_ORIGINS_FETCH_ALL;


export interface IMediasOriginsFetchAllSuccessAction 
  type: MediasActionTypes.MEDIAS_ORIGINS_FETCH_ALL;
  payload: 
    medias: MediasOrigin[]
  


export interface IMediasOriginsFetchAllFailedAction 
  type: MediasActionTypes.MEDIAS_ORIGINS_FETCH_ALL;
  payload: 
    error: string
  


/* ACTION CREATOR */
export function fetchAllMediaOrigins(): IMediasOriginsFetchAllAction 
  return 
    type: MediasActionTypes.MEDIAS_ORIGINS_FETCH_ALL
  


export type MediasAction = IMediasOriginsFetchAllAction | IMediasOriginsFetchAllSuccessAction | IMediasOriginsFetchAllFailedAction

然后是我的 epic.ts 文件:

import  from, Observable, of  from "rxjs"
import  map, catchError, switchMap, filter  from "rxjs/operators"
import  isOfType  from "typesafe-actions"
import  Epic  from "redux-observable"

import ApiClient from "agent" // change, wrapper around superagent (http client)
import IStore from "reducers"
import 
  MediasAction,
  MediasActionTypes,
  IMediasOrigin
 from "./actions"

const getAllMediasOriginEpic: Epic<MediasAction, MediasAction, IStore> = action$ => 
  const request = new ApiClient();

  return action$.pipe(
    filter(isOfType(MediasActionTypes.MEDIAS_ORIGINS_FETCH_ALL)),
    switchMap(action => // change
      from(request.getAllOriginMedias()).pipe( // change
        map((response: IMediasOrigin[]) // change =>
          of(
            type: MediasActionTypes.MEDIAS_ORIGINS_FETCH_ALL_SUCCESS,
            payload:  medias: response ,
          )
        ),
        catchError((error: Error) // change =>
          of(
            type: MediasActionTypes.MEDIAS_ORIGINS_FETCH_ALL_FAILED,
            payload: error: Error.message, // change
          )
        )
      )
    )
  )


export default combineEpics(getAllMediasOriginEpic)

我是 TypeScript 的新手,所以对于爱好者来说,错误似乎很明显,但根据 TypeScript 的消息,我不明白我做错了什么。

(不再相关)史诗中返回的action$ 处的消息点:

Type 'Observable<IMediasOriginsFetchAllAction | Observable< type: MediasActionTypes; payload:  medias: unknown; ; >>' is not assignable to type 'Observable<MediasAction>'.
  Type 'IMediasOriginsFetchAllAction | Observable< type: MediasActionTypes; payload:  medias: unknown; ; >' is not assignable to type 'MediasAction'.
    Type 'Observable< type: MediasActionTypes; payload:  medias: unknown; ; >' is not assignable to type 'MediasAction'.
      Type 'Observable< type: MediasActionTypes; payload:  medias: unknown; ; >' is missing the following properties from type 'IMediasOriginsFetchAllSuccessAction': type, payload  TS2322

我不知道我能做些什么来解决这个问题,从消息看来,我所做的一切都是错误的,并且媒体是未知的,尽管它在界面中被指定为 MediasOrigin 的数组。我在这里遗漏了什么吗?

有关信息,以下是软件包版本:

打字稿:4.0.2 redux-observable:1.2.0 rxjs:6.6.3

// 编辑:09/24/2020

为了使它更简单,我做了一些更改,并了解了为什么错误消息在谈论 mediasUnknown:我没有在史诗中说明从 api 调用映射的响应是 MediasOrigin[](重命名为 IMediasOrigin[ ] 现在)。我还删除了我自己的自定义运算符 api 方法。已对上述脚本进行了更改,并通过// change 评论发出信号。

现在是错误消息:

TypeScript error in /Users/utopiad/Dev/spectral-core/platform/src/components/Medias/epics.ts(11,7):
Type '(action$: ActionsObservable<MediasAction>) => Observable<Observable< type: MediasActionTypes; payload:  medias: IMediasOrigin[]; ; > |  ...; >' is not assignable to type 'Epic<MediasAction, MediasAction, IStore, any>'.
  Type 'Observable<Observable< type: MediasActionTypes; payload:  medias: IMediasOrigin[]; ; > |  type: MediasActionTypes; payload:  ...; ; >' is not assignable to type 'Observable<MediasAction>'.
    Type 'Observable< type: MediasActionTypes; payload:  medias: IMediasOrigin[]; ; > |  type: MediasActionTypes; payload:  ...; ; ' is not assignable to type 'MediasAction'.
      Type 'Observable< type: MediasActionTypes; payload:  medias: IMediasOrigin[]; ; >' is not assignable to type 'MediasAction'.
        Type 'Observable< type: MediasActionTypes; payload:  medias: IMediasOrigin[]; ; >' is missing the following properties from type 'IMediasOriginsFetchAllFailedAction': type, payload

据我所知,我的 IMediasOriginsFetchAllSuccessAction 和 IMediasOriginsFetchAllFailedAction 在有效负载级别之间似乎存在冲突,这使得其中一个无法分配给类型 MediasAction。但这不就是 | 的全部目的吗?创建类型时的运算符?

【问题讨论】:

如果在api('...') 之后立即添加mergeAll() 会怎样。原因是 api 本身创建了一个您尚未订阅的 observable,这意味着链中的下一个 map 运算符将收到 observable,而不是其实际结果。 感谢您的洞察力,不幸的是,从那时起我进行了更改并删除了我自己的 api 运算符。所有更改均已反映在原始帖子中。 我认为这是同样的问题。您的 map 运算符返回一个可观察的 of(...)。尝试删除 of() 并简单地从中返回对象 type: MediasActionTypes.MEDIAS_ORIGINS_FETCH_ALL_SUCCESS, payload: medias: response , 它在错误消息中添加了两行:Types of property 'type' are incompatible. Type 'MediasActionTypes' is not assignable to type 'MediasActionTypes.MEDIAS_ORIGINS_FETCH_ALL'. 是的,我在codesandbox上复制了它:codesandbox.io/s/epics-and-typescript-9uigq 【参考方案1】:

您可以通过在返回操作时将as const 添加到您的MediasActionTypes.* 来解决此问题:

const getAllMediasOriginEpic: Epic<MediasAction, MediasAction> = (action$) => 
  const request = new ApiClient();
  return action$.pipe(
    filter(isOfType(MediasActionTypes.MEDIAS_ORIGINS_FETCH_ALL)),
    switchMap((action) =>
      from(request.getAllOriginMedias()).pipe(
        map((response: IMediasOrigin[]) => (
          type: MediasActionTypes.MEDIAS_ORIGINS_FETCH_ALL_SUCCESS as const,
          payload:  medias: response 
        )),
        catchError((error: Error) =>
          of(
            type: MediasActionTypes.MEDIAS_ORIGINS_FETCH_ALL_FAILED as const,
            payload:  error: error.message 
          )
        )
      )
    )
  );
;

这是因为当你使用这样的东西时:

map((response: IMediasOrigin[]) => (
    type: MediasActionTypes.MEDIAS_ORIGINS_FETCH_ALL_SUCCESS,
    payload:  medias: response 
  )),

Observable 的类型是: type: MediasActionTypes , ...。正如您在错误中看到的那样

Type 'MediasActionTypes' is not assignable to type 'MediasActionTypes.MEDIAS_ORIGINS_FETCH_ALL_FAILED'

'MediasActionTypes' 指联合体,MediasActionTypes.MEDIAS_ORIGINS_FETCH_ALL_FAILED具体 类型。因此,通过使用 type: MediasActionTypes.MEDIAS_ORIGINS_FETCH_ALL_SUCCESS as const,您可以缩小类型。

【讨论】:

非常感谢您的帮助,确实解决了我的问题。 很高兴能帮上忙!

以上是关于如何使用 TypeScript 为 Redux-Observable 的 Epic 输入 Actions的主要内容,如果未能解决你的问题,请参考以下文章

如何将 Relay 查询从 TypeScript 转换为 ES5?

Typescript - 如何禁止两个解析为相同类型的类型别名互换使用?

在 Typescript 中,如何使用扩展语法为 querySelectorAll 设置类型

如何使用 Jasmine 为私有方法编写 Angular / TypeScript 单元测试

如何使用 Typescript 和 useState 将类型对象设置为 React 的初始值?

如何使用 TypeScript 为 Redux-Observable 的 Epic 输入 Actions