4

与世界上的应用程序一样,我的 React 应用程序需要对 API 执行一些 Ajax 调用。

我选择使用 Redux 中间件,以便正确地将 API 获取与我的组件分开。这个想法是从我的组件
中调度操作。REQUEST中间件监听它们并调度SUCCESSERROR操作:这些最后由减速器监听。

这里的很多人已经问过如何从 Redux 中间件发送操作:这不是我的问题的主题 :)

首先,让我向您展示一个我用来编写的简单 reducer:

function currentUserReduxer(state = {}, action = {}) {
  const { currentUser, error } = action.payload;

  switch (action.type) {
    case 'GET_CURRENT_USER_REQUEST':
      return { ...state, isFetching: true, error: null };

    case 'GET_CURRENT_USER_SUCCESS':
      return { ...state, id: currentUser.id, isFetching: false };

    case 'GET_CURRENT_USER_FAILURE':
      return { ...state, isFetching: false, error };

    default:
      return state;
  }
}

以及相应的中间件:

() => next => async (action) => {
  next(action);

  switch (action.type) {
    case'GET_CURRENT_USER_REQUEST': {
      try {
        const currentUser = await api.getCurrentUser();
        next(actions.getCurrentUserSuccess(currentUser));
      } catch (error) {
        next(actions.getCurrentUserFailure(error));
      }
      break;
    }

    default:
      break;
  }
};

很长一段时间都运行良好,然后我意识到它有一部分是错误的:我的中间件没有返回 的值next,所以它破坏了中间件链,这是错误的!
由于next(action);是我在中间件中执行的第一件事,所以我不能这么快就返回它,所以我把它移到了中间件的末尾。我还决定调度新的动作而不是使用next它们(毕竟,它们是新动作,将它们发送到整个中间件链确实有意义)。我的新中间件现在看起来像这样:

store => next => async (action) => {
  switch (action.type) {
    case'GET_CURRENT_USER_REQUEST': {
      try {
        const currentUser = await api.getCurrentUser();
        store.dispatch(actions.getCurrentUserSuccess(currentUser));
      } catch (error) {
        store.dispatch(actions.getCurrentUserFailure(error));
      }
      break;
    }

    default:
      break;
  }

  return next(action);
};

它看起来很棒,但我现在有另一个问题:因为store.dispatch是同步的,next(action)所以在之后调用。这意味着我的减速器在orREQUEST之后收到动作SUCCESSFAILURE:(

我认为一种解决方案可能是使用良好的旧承诺,而不是await

store => next => async (action) => {
  switch (action.type) {
    case'GET_CURRENT_USER_REQUEST': {
      api.getCurrentUser()
        .then((currentUser) => {
          store.dispatch(actions.getCurrentUserSuccess(currentUser));
        })
        .catch((error) => {
          store.dispatch(actions.getCurrentUserFailure(error));
        });
      break;
    }

    default:
      break;
  }

  return next(action);
};

另一个想法是store.dispatch用 a包装setTimeout

store => next => async (action) => {
  switch (action.type) {
    case'GET_CURRENT_USER_REQUEST': {
      try {
        const currentUser = await api.getCurrentUser();

        setTimeout(() => store.dispatch(actions.getCurrentUserSuccess(currentUser)));
      } catch (error) {
        setTimeout(() => store.dispatch(actions.getCurrentUserFailure(error)));
      }
      break;
    }

    default:
      break;
  }

  return next(action);
};

老实说,我不是很喜欢这两种解决方案,它们感觉很hacky...

所以这是我的问题:我应该如何处理我的问题?有没有更干净的方法来做到这一点?

提前致谢 :)

4

2 回答 2

3

I think your first solution is near to the clean answer. You can introduce the term sideEffects which are async functions executed whenever your action is dispatch. Your middleware function is still synchronize and the action will be dispatch right away. Here is an example:

getCurrentUserSideEffects = async (action) => {
    api.getCurrentUser()
        .then((currentUser) => {
          store.dispatch(actions.getCurrentUserSuccess(currentUser));
        })
        .catch((error) => {
          store.dispatch(actions.getCurrentUserFailure(error));
        });
}

<other side effects>

store => next => action => {
  switch (action.type) {
    case 'GET_CURRENT_USER_REQUEST': {
      getCurrentUserSideEffects(action);
      break;
    }

    default:
      break;
  }

  return next(action);
};

I think this idea is pretty similar to redux saga, but simpler and easier to understand.

于 2019-04-22T15:03:05.327 回答
2

似乎您尝试做的类似于Redux-Saga我建议您看看他们的库。

取自他们的例子

// worker Saga: will be fired on USER_FETCH_REQUESTED actions
function* fetchUser(action) {
   try {
      const user = yield call(Api.fetchUser, action.payload.userId);
      yield put({type: "USER_FETCH_SUCCEEDED", user: user});
   } catch (e) {
      yield put({type: "USER_FETCH_FAILED", message: e.message});
   }
}

/*
  Starts fetchUser on each dispatched `USER_FETCH_REQUESTED` action.
  Allows concurrent fetches of user.
*/
function* mySaga() {
  yield takeEvery("USER_FETCH_REQUESTED", fetchUser);
}
于 2018-07-05T08:23:08.223 回答