5

Redux 工具包提供了createAction辅助方法。我无法将使用createActionas 类型创建的操作引用。

例如

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

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

我怎么知道exampleSagaaction是类型getData



要查看我是如何尝试这样做的,请参阅以下(示例)代码并查看我所面临问题的评论:

减速器.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);
}
4

2 回答 2

6

I did some more digging and here's how I got around the issue.

An action could be created without tying it to a SliceCaseReducer: (Note that the created getData below is not an Action per se rather an ActionCreator).

common.ts

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

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

getData is already cooked to be dispatched from a FunctionComponent if given the correct payload:

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;

With Sagas

To have a saga listen to the action

Saga Effects accept string type of an action to react. Luckily, actionCreators expose a type property representing the defined string name of the action.

saga.ts (listener)

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
}

Specifying action as argument of a saga.

Specifying the action as argument to a saga is a bit tricky, we have to use TypeScript's Type Guards. Interestingly, it's mentioned in the redux-toolkit's documentation as well. In short, we have to use actionCreator.match method of the actionCreator to discriminate down the passed action to the desired type. Thus, after discrimination, we receive the desired static typing for the matched action's payload.

saga.ts (worker)

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));
  }
}
于 2020-03-17T23:20:25.500 回答
6

这就是使用 getData 的返回类型获取它的方式。

function* exampleSaga(action: ReturnType<typeof getData>) {

...

}
于 2020-11-21T19:26:13.593 回答