Redux Toolkit: как ссылаться на действие, созданное с помощью createAction, как на тип - PullRequest
1 голос
/ 24 февраля 2020

Redux предоставляет вспомогательный метод createAction. У меня возникают проблемы при указании действий, созданных с createAction, как типа.

Например :

const getData = createAction<PayloadAction<number>>('user/getData')

function* exampleSaga(action: ?) {
   ...
}

Как я могу сказать exampleSaga что action имеет тип getData?



Чтобы увидеть, как я пытался это сделать, см. ниже ( пример) код и см. комментарии к проблеме, с которой я столкнулся:

reducer.ts

import { createAction, PayloadAction } from '@reduxjs/toolkit';

export const getData = createAction<PayloadAction<number>>('user/getData');

const userSlice = createSlice({
  name: userSliceName,
  initialState,
  reducers: {
    getUser(state: IUserState): void {
      state.loading = true;
      state.error = null;
    },
    getUserSuccess(state: IUserState, action: PayloadAction<{ user: IUser }>): void {
      const { user } = action.payload;
      state.user = user;
      state.loading = false;
      state.error = null;
    },
    getUserFailure(state: IUserState, action: PayloadAction<string>): void {
      state.loading = false;
      state.error = action.payload;
    },
  },
});

export type UserSaga = Generator<
  | CallEffect<IUser>
  | PutEffect<typeof getUserSuccess>
  | PutEffect<typeof getUserFailure>
  | ForkEffect<never>
>;

sagas.ts

import { UserSaga, getData } from './reducer.ts';

// For the following fetchUser saga arguments, how can I define that 'action' is of type getData, imported from the above 'reducer.ts' file.
function* fetchUser(action: PayloadAction<typeof getData>): UserSaga {
  try {
    const userId = action.payload;
    // Problem is here
    // I expect userId to be of type number, whereas it is:
    // ActionCreatorWithPayload<{payload: number, type: string}>
    ...
  } catch (e) {
    ...
  }
}

function* userSaga(): UserSaga {
  const pattern: any = getData.type;
  yield takeLatest(pattern, fetchUser);
}

1 Ответ

0 голосов
/ 18 марта 2020

Я сделал еще несколько копаний, и вот как я обошел проблему.

Действие можно создать, не привязывая его к SliceCaseReducer : (Обратите внимание, что созданное getData ниже это не действие само по себе, а ActionCreator ).

common.ts

import { createAction } from '@reduxjs/toolkit';

export const getData = createAction<number>('getData');

getData уже приготовлено для отправки из FunctionComponent, если задана правильная полезная нагрузка:

component.tsx

import React, { useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { useParams } from 'react-router';
import { getData } from './state/common';

const User: React.FC = (): React.ReactElement => {
  const { userId } = useParams();
  const dispatch = useDispatch();

  useEffect((): void => {
    dispatch(getData(Number(userId)));
  }, []);

  return (<>User Component</>);
};

export default UserContainer;

с Sagas

Для получения сага слушайте действие

Saga Effects принимает строковый тип действия для реакции. К счастью, actionCreator s предоставляет свойство type , представляющее определенное строковое имя действия.

saga.ts (слушатель)

import { takeLatest } from 'redux-saga/effects';
import { getData } from './common';

function* userSaga() {
  // getData.type could be input to a Saga Effect
  yield takeLatest(getData.type, fetchUser); // fetchUser is a worker saga explained below
}

Задание действия в качестве аргумента саги.

Задать действие в качестве аргумента саги немного сложно, мы должны использовать TypeScript's Type Guards . Интересно отметить, что это упоминается и в документации о комплектации . Короче говоря, мы должны использовать actionCreator.match метод actionCreator, чтобы различить переданное действие до желаемого типа. Таким образом, после различения мы получаем желаемый набор c для полезной нагрузки соответствующего действия.

saga.ts (работник)

import { Action } from '@reduxjs/toolkit';
import { call, put } from 'redux-saga/effects';
import * as userApi from '../../../api/user-api';
import { getData } from './common';
import { getUserSuccess, getUserFailure } from './user';

// Note that we can't directly specify that fetchUser only accepts getData action type.
function* fetchUser(action: Action) {
  try {
    // getData.match could be used to assert that we are only concerned with getData action type
    if (getData.match(action)) {
      // Inside the body of if, we get correct static typing
      const userId = action.payload;

      const fetchedUser = yield call(userApi.getUser, userId);
      yield put(getUserSuccess({user: fetchedUser}));
    }
  } catch (e) {
    yield put(getUserFailure(e.message));
  }
}
...