web-dev-qa-db-fra.com

React + Redux-Observable + TypeScript - Compilation, erreur non attribuable d'argument

Je crée une application en utilisant React et Redux-Observable. Je suis nouveau à cela et j'essaie de créer une épopée pour exécuter le login de l'utilisateur.

Mon épopée est ci-dessous:

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))),
      ),
    ),
  );

Le problème est que je reçois une erreur TypeScript sur cette ligne: ofType<LoginAction>(LoginActionTypes.LOGIN_ACTION) en disant ceci:

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

Mes actions sont ici:

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;

Comment puis-je résoudre ce problème sans utiliser les types any sur Epic?

7
Felipe

L'opérateur ofType proposé par redux-observable n'est pas le meilleur moyen de discriminer les types d'union. Un meilleur moyen consiste à utiliser la fonction isOfType fournie par typesafe-actions .

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

Commençons par expliquer à TypeScript les actions possibles utilisées dans votre application. Votre flux d'actions ne doit pas être défini comme ActionsObservable<Action>, mais comme un flux de vos actions: ActionsObservable<LoginActions>.

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

Nous pouvons maintenant utiliser le prédicat isOfType avec l'opérateur filter. Remplacez ceci:

ofType<LoginAction>(LoginActionTypes.LOGIN_ACTION)

avec ça:

filter(isOfType(LoginActionTypes.LOGIN_ACTION))

L'action transmise dans le flux sera correctement reconnue en tant que LoginAction.

3
Karol Majewski

TypeScript génère une erreur car vous avez défini sur ActionsObservable action generic une redux Action qui est une interface au format { type: string }. Donc, TypeScript pense que toutes les actions à venir auront uniquement un type, mais vous définissez l'opérateur de filtrage ofType sur un autre type d'action, même s'il étend l'interface redux Action TypeScript nécessitera bivarianceHack pour vous permettre de transmettre tout ce qui implémente cette interface. Une des solutions de contournement les plus simples consiste à remplacer ActionsObservable<Action> par ActionsObservable<AnyAction>, avec AnyAction qui est importé de redux.

0
iamawebgeek

Vous pouvez commander https://github.com/piotrwitek/react-redux-TypeScript-guide pour les détails ici pour toutes les méthodes habituelles d'utilisation de React, Redux et Redux-Observable.

Je suggère d'utiliser la bibliothèque typesafe-actions pour obtenir les types.

Quelques pseudo codes:

Actes

Au lieu de cela

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

utiliser typesafe-actions, sans interface

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>

Puis exportez toutes les actions.

actions/index.ts

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

export default type AllActions = LoginActionCreator

Epics

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))),
      ),
    ),
  );

Où les epics sont de la bibliothèque redux-observable, AllActions sont les actions qui sont entrées et sorties des epics.

Les types sont les suivants:

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

Si vous voulez utiliser store de redux, vous avez besoin d’un RootState (de root réducteur).

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);
  })
0
johnson lai