Я создаю приложение, используя 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;
Как я могу это исправить, не используя типы any на Epic?





Оператор ofType, предлагаемый redux-observable, - не лучший способ различать типы объединения. Намного лучший способ - использовать функцию isOfType, предоставляемую typesafe-actions.
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.
Реализация довольно проста и портативна. Вы, вероятно, можете скопировать его и вставить в свой проект, если хотите. github.com/piotrwitek/typesafe-actions/blob/…
В итоге я реализовал метод (ы) typesafe-actions. И мне это очень нравится - мои действия и редукторы внезапно стали намного «чище».
Удивительно, это спасло меня после нескольких часов
Вы можете проверить 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 * as 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, вам понадобится RootState (из корневого редуктора)
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);
})
Typescript выдает ошибку, потому что вы установили для действия ActionsObservable общее сокращение Action, которое является интерфейсом с формой { type: string }. Таким образом, Typescript считает, что все поступающие действия будут иметь только тип, но вы устанавливаете для оператора фильтрации ofType другой тип действия, даже если он расширяет интерфейс действий redux. Для Typecript потребуется bivarianceHack, чтобы вы могли передавать все, что реализует этот интерфейс. Один из простейших обходных путей - заменить ActionsObservable<Action> на ActionsObservable<AnyAction>, используя AnyAction, который импортируется из redux.
Есть ли способ решить эту проблему без использования
typesafe-actions?