如何避免对 redux thunk 使用单独的 _PENDING _FULFILLED 和 _REJECTED 操作?

How do I avoid using separate _PENDING _FULFILLED and _REJECTED actions with redux thunk?

我正在用分派 _PENDING_FULFILLED_REJECTED 动作的 thunk 编写我的动作和 reducers。但是,我想要一个更好的解决方案来避免样板文件。我正在迁移到 Typescript,它通过为每个 _PENDING_FULFILLED_REJECTED 操作要求一个接口来使这个样板加倍。它正在失控。有没有一种方法可以获得我的代码的 same/similar 功能,而无需每个 thunk 具有三种操作类型?

localUserReducer.js

const initialState = {
  fetching: false,
  fetched: false,
  user: undefined,
  errors: undefined,
};

export default function (state = initialState, action) {
  switch (action.type) {
    case 'GET_USER_PENDING':
      return {
        ...state,
        fetching: true,
      };
    case 'GET_USER_FULFILLED':
      return {
        ...state,
        fetching: false,
        fetched: true,
        user: action.payload,
      };
    case 'GET_USER_REJECTED':
      return {
        ...state,
        fetching: false,
        errors: action.payload,
      };
    default:
      return state;
  }
}

localUserActions.js

import axios from 'axios';

export const getUser = () => async (dispatch) => {
  dispatch({ type: 'GET_USER_PENDING' });
  try {
    const { data } = await axios.get('/api/auth/local/current');
    dispatch({ type: 'GET_USER_FULFILLED', payload: data });
  } catch (err) {
    dispatch({ type: 'GET_USER_REJECTED', payload: err.response.data });
  }
};

新手可能对redux-thunk有很大的误解。如果我使用此处记录的 Typescript 和 redux-thunk 的实现,我不明白如何发送 _REJECTED 操作:https://redux.js.org/recipes/usage-with-typescript#usage-with-redux-thunk

有一种方法可以在每个 thunk 没有三个动作类型的情况下获得类似的功能,但它会对渲染逻辑产生一些影响。

我建议将异步调用的瞬态方面下推到数据。因此,与其将 actions 标记为 _PENDING_FULFILLED_REJECTED,不如将 data 标记为方式,并进行一次操作。

localUser.js(用户类型的新文件)

// Use a discriminated union here to keep inapplicable states isolated
type User =
  { status: 'ABSENT' } |
  { status: 'PENDING' } |
  { status: 'FULLFILLED', data: { fullName: string } } |
  { status: 'REJECTED', error: string };

// a couple of constructors for the fullfilled and rejected data
function dataFulFilled(data: { fullName: string }) {
  return ({ status: 'FULLFILLED', data });
}

function dataRejected(error: string) {
  return ({ status: 'REJECTED', error });
}

localUserReducer.js

const initialState: { user: User } = { user: { status: 'ABSENT' } };

export default function (state = initialState, action): { user: User } {
  switch (action.type) {
    case 'USER_CHANGED':
      return {
        ...state,
        user: action.payload
      };
    default:
      return state;
  }
}

localUserActions.js

import axios from 'axios';

export const getUser = () => async (dispatch) => {
  dispatch({ type: 'USER_CHANGED', payload: { status: 'PENDING' } });
  try {
    const { data } = await axios.get('/api/auth/local/current');
    dispatch({ type: 'USER_CHANGED', payload: dataFulFilled(data) });
  } catch (err) {
    dispatch({ type: 'USER_CHANGED', payload: dataRejected(err.response.data) });
  }
};

这也将消除对多个布尔字段(fetchingfetched)的需要,并将各种数据状态与意外修改隔离开来。

渲染逻辑的更改将是必要的,但很可能是一种改进。不是使用布尔值组合嵌套的 if-else 语句,而是可以使用单个开关来处理数据状态的四种情况。

然后你可以从你的渲染函数调用这样的东西...

function userElement(user: User) {
  switch (user.status) {
    case 'ABSENT':
      return <></>;
    case 'PENDING':
      return <div>Fetching user information...Please be patient...</div>;
    case 'FULLFILLED':
      return <div>{user.data.fullName}</div>;
    case 'REJECTED':
      return <h1>The error is: {user.error}</h1>
  }
}

希望对您有所帮助。祝你好运!