使用 Redux Toolkit 和 Typescript 反应可观察的史诗

edo*_*edo 3 typescript reactjs redux redux-observable redux-toolkit

我不确定如何使用 Redux Toolkit 和 Typescript 编写 React observable 史诗。

假设我有这个 authSlice:

import { CaseReducer, createSlice, PayloadAction } from "@reduxjs/toolkit";

type AuthState = {
  token: string,
  loading: boolean,
};

const initialState: AuthState = {
  token: "",
  loading: false,
};

const loginStart: CaseReducer<AuthState, PayloadAction<{username: string, password: string}>> = (state, action) => ({
  ...state,
  loading: true,
  token: "",
});

const loginCompleted: CaseReducer<AuthState, PayloadAction<{token: string}>> = (state, action) => ({
  ...state,
  loading: false,
  token: action.payload.token,
});

const authSlice = createSlice({
  name: 'auth',
  initialState,
  reducers: {
    loginStart,
    loginCompleted,
  },
});

export default authSlice;
Run Code Online (Sandbox Code Playgroud)

还有这家店:

import { configureStore } from '@reduxjs/toolkit';
import { combineEpics, createEpicMiddleware } from 'redux-observable';
import authEpic from './epics/authEpic';
import authSlice from './slices/authSlice';

const epicMiddleware = createEpicMiddleware();

export const rootEpic = combineEpics(
  authEpic
);

const store = configureStore({
  reducer: {
    auth: authSlice.reducer,
  },
  middleware: [epicMiddleware]
});

epicMiddleware.run(rootEpic);

export type RootState = ReturnType<typeof store.getState>;
export default store;
Run Code Online (Sandbox Code Playgroud)

我应该如何写这个 authEpic(我希望目的是不言自明的):

import { Action, Observable } from 'redux';
import { ActionsObservable, ofType } from 'redux-observable';
import { ajax } from 'rxjs/ajax';
import { switchMap } from 'rxjs/operators';
import authSlice from '../slices/authSlice';

export default (action$: ActionsObservable<???>) => action$.pipe(
  ofType(???), /* should be of type loginStart */
  switchMap<???,???>(action => ajax.post( // should be from a loginStart action to {token: string}
    "url", {
      username: action.payload.username, 
      password: action.payload.password 
    }
  )),
  ...
);
Run Code Online (Sandbox Code Playgroud)

我完全困惑于???这就是应该是什么类型以及 redux observable 应该如何与 redux 工具包链接。

任何提示?

phr*_*hry 8

在 redux-toolkit 中,您应该action.match在 afilter而不是ofType类似的工作流程中使用该函数,如文档中所述

文档中的这个示例适用于所有 RTK 操作,无论是使用createAction,createSlice还是createAsyncThunk.

import { createAction, Action } from '@reduxjs/toolkit'
import { Observable } from 'rxjs'
import { map, filter } from 'rxjs/operators'

const increment = createAction<number>('INCREMENT')

export const epic = (actions$: Observable<Action>) =>
  actions$.pipe(
    filter(increment.match),
    map((action) => {
      // action.payload can be safely used as number here (and will also be correctly inferred by TypeScript)
      // ...
    })
  )
Run Code Online (Sandbox Code Playgroud)


Lin*_*ste 7

问题是 redux-toolkit 掩盖了操作,因此很难知道操作类型是什么。而在传统的 redux 设置中,它们只是一堆常量。

type T = ReturnType<typeof authSlice.actions.loginStart>['type']; // T is string

// have to create an action to find the actual value of the string
const action = authSlice.actions.loginStart({username: "name", password: "pw"});
const type = action.type;
console.log(type);
Run Code Online (Sandbox Code Playgroud)

看来action.type创建的操作authSlice.actions.loginStart是“auth/loginStart”,其类型只是而string不是特定的字符串文字。公式为${sliceName}/${reducerName}。所以就ofType变成了

ofType("auth/loginStart")
Run Code Online (Sandbox Code Playgroud)

现在是通用注释。我们authEpic正在执行登录开始操作并将其转换为登录完成操作。我们可以通过查看以下方式以迂回的方式获得这两种类型authSlice

type LoginStartAction = ReturnType<typeof authSlice.actions.loginStart>`)
Run Code Online (Sandbox Code Playgroud)

但这很愚蠢,因为我们在创建时就已经知道了动作类型authSlice。动作类型是PayloadAction你的内部CaseReducer。让我们别名并导出它们:

export type LoginStartAction = PayloadAction<{ username: string; password: string }>;

export type LoginCompletedAction = PayloadAction<{ token: string }>;
Run Code Online (Sandbox Code Playgroud)

这些是您将用于大小写缩减器的类型:

const loginStart: CaseReducer<AuthState, LoginStartAction> = ...

const loginCompleted: CaseReducer<AuthState, LoginCompletedAction> = ...
Run Code Online (Sandbox Code Playgroud)

我不太熟悉可观察的和史诗,但我认为你想要的类型authEpic是:

export default (action$: ActionsObservable<LoginStartAction>) => action$.pipe(
    ofType("auth/loginStart"),
    switchMap<LoginStartAction, ObservableInput<LoginCompletedAction>>(
        action => ajax.post(
            "url", action.payload
        )
    )
    ...
);
Run Code Online (Sandbox Code Playgroud)