React + Redux-Observable + Typescript - компиляция, ошибка аргумента не назначается

Я создаю приложение, используя 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?

Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Навигация по приложениям React: Исчерпывающее руководство по React Router
Навигация по приложениям React: Исчерпывающее руководство по React Router
React Router стала незаменимой библиотекой для создания одностраничных приложений с навигацией в React. В этой статье блога мы подробно рассмотрим...
Массив зависимостей в React
Массив зависимостей в React
Все о массиве Dependency и его связи с useEffect.
11
0
6 799
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Оператор 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.

Есть ли способ решить эту проблему без использования typesafe-actions?

Fredrik Norlin 23.10.2019 13:19

Реализация довольно проста и портативна. Вы, вероятно, можете скопировать его и вставить в свой проект, если хотите. github.com/piotrwitek/typesafe-actions/blob/…

Karol Majewski 23.10.2019 19:24

В итоге я реализовал метод (ы) typesafe-actions. И мне это очень нравится - мои действия и редукторы внезапно стали намного «чище».

Fredrik Norlin 24.10.2019 12:56

Удивительно, это спасло меня после нескольких часов

George S 12.04.2021 18:28

Вы можете проверить 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.

Другие вопросы по теме