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?
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
.
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.
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);
})