React + Redux-Observable +打字稿-编译,参数不可分配错误

时间:2018-11-21 15:28:37

标签: reactjs typescript rxjs redux-observable

我正在使用React和Redux-Observable创建一个应用程序。我对此并不陌生,我试图创建一个史诗来执行用户登录。

我的史诗如下:

export const loginUserEpic = (action$: ActionsObservable<Action>) =>
  action$.pipe(
    ofType<LoginAction>(LoginActionTypes.LOGIN_ACTION),
    switchMap((action: LoginAction) =>
      ajax({
        url,
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: { email: action.payload.username, password: action.payload.password },
      }).pipe(
        map((response: AjaxResponse) => loginSuccess(response.response.token)),
        catchError((error: Error) => of(loginFailed(error))),
      ),
    ),
  );

问题在于,我在此行上遇到Typescript错误:ofType<LoginAction>(LoginActionTypes.LOGIN_ACTION)这样说:

Argument of type '(source: Observable<LoginAction>) => Observable<LoginAction>' is not assignable to parameter of type 'OperatorFunction<Action<any>, LoginAction>'.
  Types of parameters 'source' and 'source' are incompatible.
    Type 'Observable<Action<any>>' is not assignable to type 'Observable<LoginAction>'.
      Type 'Action<any>' is not assignable to type 'LoginAction'.
        Property 'payload' is missing in type 'Action<any>'.

我的动作在这里:

export enum LoginActionTypes {
  LOGIN_ACTION = 'login',
  LOGIN_SUCCESS_ACTION = 'login-sucesss',
  LOGIN_FAILED_ACTION = 'login-failed',
}

export interface LoginAction extends Action {
  type: LoginActionTypes.LOGIN_ACTION;
  payload: {
    username: string;
    password: string;
  };
}

export function login(username: string, password: string): LoginAction {
  return {
    type: LoginActionTypes.LOGIN_ACTION,
    payload: { username, password },
  };
}

export interface LoginSuccessAction extends Action {
  type: LoginActionTypes.LOGIN_SUCCESS_ACTION;
  payload: {
    loginToken: string;
  };
}

export function loginSuccess(loginToken: string): LoginSuccessAction {
  return {
    type: LoginActionTypes.LOGIN_SUCCESS_ACTION,
    payload: { loginToken },
  };
}

export interface LoginFailedAction extends Action {
  type: LoginActionTypes.LOGIN_FAILED_ACTION;
  payload: {
    error: Error;
  };
}

export function loginFailed(error: Error): LoginFailedAction {
  return {
    type: LoginActionTypes.LOGIN_FAILED_ACTION,
    payload: { error },
  };
}

export type LoginActions = LoginAction | LoginSuccessAction | LoginFailedAction;

如何在Epic上不使用any类型来解决此问题?

3 个答案:

答案 0 :(得分:4)

ofType提供的redux-observable运算符不是区分联合类型的最佳方法。更好的方法是使用typesafe-actions提供的isOfType函数。

import { filter } from 'rxjs/operators';
import { isOfType } from 'typesafe-actions';

首先,让我们告诉TypeScript应用程序中可能使用的操作。您的操作流不应定义为ActionsObservable<Action>,而应定义为您的操作流:ActionsObservable<LoginActions>

export const loginUserEpic = (action$: ActionsObservable<LoginActions>) =>

现在,我们可以将isOfType谓词与filter运算符一起使用。替换为:

ofType<LoginAction>(LoginActionTypes.LOGIN_ACTION)

与此:

filter(isOfType(LoginActionTypes.LOGIN_ACTION))

沿着流传递的动作将被正确识别为LoginAction

答案 1 :(得分:0)

您可以在此处查看https://github.com/piotrwitek/react-redux-typescript-guide的详细信息,以了解使用React,Redux和Redux-Observable时的所有标准操作方式。

我建议使用typesafe-actions库来实现类型。

一些伪代码:

操作

代替这个

export interface LoginSuccessAction extends Action {
  type: LoginActionTypes.LOGIN_SUCCESS_ACTION;
  payload: {
    loginToken: string;
  };
}

export function loginSuccess(loginToken: string): LoginSuccessAction {
  return {
    type: LoginActionTypes.LOGIN_SUCCESS_ACTION,
    payload: { loginToken },
  };
}

使用typesafe-actions,不带界面

actions/login/LoginActions.ts

import {action} from "typesafe-actions"

export function loginSuccess(loginToken: string) {
  return action(LoginActionTypes.LOGIN_SUCCESS_ACTION, { loginToken });
}

actions/login/LoginActionsModel.ts

import * LoginActions from "./LoginActions";
import { ActionCreator } from "typesafe-actions";

export type LoginActionCreator = ActionCreator<typeof LoginActions>

然后导出所有动作。

actions/index.ts

import { LoginActionCreator } from "./login/LoginActionModel";

export default type AllActions = LoginActionCreator

史诗

import { Epic } from "redux-observable";
import { isOfType } from "typesafe-actions";
import { filter } from "rxjs/operators";
export const loginUserEpic: Epic<AllActions> = (action$) =>
  action$.pipe(
    filter(isOfType((LoginActionTypes.LOGIN_ACTION))),
    switchMap((action: LoginAction) =>
      ajax({
        url,
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: { email: action.payload.username, password: action.payload.password },
      }).pipe(
        map((response: AjaxResponse) => loginSuccess(response.response.token)),
        catchError((error: Error) => of(loginFailed(error))),
      ),
    ),
  );

“史诗”来自redux-observable库,AllActions是史诗的输入和输出。

类型如下:

Epic<InputActions, OutputActions, Store>
Epic<Actions(Input&Output)>

如果要使用Redux中的store,则需要RootState(来自root reducer)

export const someEpic: Epic<AllActions, AllActions, RootState> = (action$, store$) 
=> action$.pipe(
  filter(isOfType(SOMETYPE)),
  mergeMap(action => {
    const a = store$.value.a;
    return someActions(a, action.payload.b);
  })

答案 2 :(得分:0)

Typescript给出错误,因为您已将ActionsObservable动作设置为通用redux Action,它是形式为{ type: string }的接口。因此,Typescript认为所有即将来临的动作将只有一个类型,但是您将ofType设置为过滤运算符另一种类型的Action,即使它扩展了redux动作接口Typescript也需要bivarianceHack才能允许您传递任何内容实现该接口。最简单的解决方法之一是将ActionsObservable<Action>更改为ActionsObservable<AnyAction>,其中AnyAction是从redux导入的。