【问题标题】:How to fire AJAX calls in response to the state changes with Redux?如何使用 Redux 触发 AJAX 调用以响应状态更改?
【发布时间】:2016-05-17 16:55:37
【问题描述】:

我正在将现有的状态模型转换为 Redux,并且大部分情况下它都很轻松。然而,我遇到的一点是转换“观察到的”状态 ajax 请求。本质上,我将某些 ajax 请求“链接”到其他状态,因此无论谁修改它们,它们总是会正确发出。我可以通过订阅 Redux 存储更新来获得类似的行为,但在侦听器中触发操作感觉就像是 hack。

一个可能的解决方案是通过 thunk 模式将逻辑移动到动作创建者。问题是我要么必须跨动作复制获取逻辑(因为多个动作可以修改“观察到的”状态),要么将大多数减速器逻辑拉到动作创建者级别。动作创建者也不应该知道减速器将如何响应发出的动作。

我可以批量处理“子动作”,因此我只需在每个动作“块”中放置适当的获取逻辑,但这似乎违反了动作产生有效状态的概念。我宁愿在动作创建者级别承担此责任。

是否有任何普遍接受的规则?这不是一个简单的应用程序,其中在与组件交互时发出临时 ajax 请求,大多数数据在多个组件之间共享,并且请求被优化和获取以响应状态变化。

TLDR; 我想触发 ajax 请求以响应状态变化,而不是在发生特定操作时。除了在订阅侦听器中触发这些操作之外,是否有更好的“Redux 特定”方式来组织 action/actionCreators 来模拟这种行为?

【问题讨论】:

  • 我不能 100% 确定我是否完全理解问题,但您是否希望作为该操作的结果调度更多操作?您可以为此使用 radux 中间件 - 查看 thunk 源代码(您已经提到了 thunk 模式,我认为这是一种很好的方法)。如果这是您正在寻找的,我可以制定答案,
  • 抱歉,我有胡​​说八道的倾向。简而言之,我想触发我的 ajax 请求以响应状态变化,而不是在发生特定操作时。除了在订阅侦听器中触发这些操作之外,还有更好的“Redux 特定”方式吗?
  • 别担心,伙计——我一直都是一样的。我对 redux 操作的看法是它们是直接的状态更改器。您触发您的状态将更改的操作,但我从未使用过订阅者或侦听器,如果我需要触发额外的“远程操作”或让两个减速器响应单个操作,如果我想更改状态,我总是使用中间件在两个地方。我将为中间件添加答案。

标签: ajax asynchronous architecture state redux


【解决方案1】:

让模块订阅状态更新和启动 Ajax 请求(在执行时触发操作)对我来说似乎很好,因为它让存储/reducer 坚定地负责触发请求。在我的大型应用程序中,所有 Ajax 请求和其他异步行为都是以这种方式完成的,因此所有动作都可以只是有效负载,没有“动作创建者”的概念。

如果可能,请避免级联同步操作。我的异步处理程序从不同步触发操作,但仅在请求完成后触发。

在我看来,这是一种比异步操作创建器更实用的方法,您可能更喜欢也可能不喜欢!

【讨论】:

  • 谢谢汤姆!如果您成功地使用了这种方法,那么我不会回避它。我很难找到其他人以这种方式为他们的应用建模,所以我不确定我是否走在正确的道路上。
【解决方案2】:

您可以使用中间件来启动远程操作以响应本地操作。

假设我有一个本地操作:

const updateField = (val) => {
  {type: UPDATE_FIELD, val}
}

还有一个输入字段:

<input type='text' onChange={this.props.updateField.bind(this.val)}>

因此,简而言之,当您在字段内输入时,它会触发您的操作,进而通过 reducer 更改状态。让我们忘记这个动作是如何传递给组件的或者 this.val 是什么 - 我们只是假设这已经解决并且正在工作。

这个设置一切都很好,但它只会在本地改变你的状态。要更新服务器,您必须触发另一个操作。让我们构建它:

const updateFieldOnServer = (val) => {
  return (dispatch) => {
    MAKE_AJAX.done(
      FIRE_SOME_ACTIONS_ON_SUCCESS
    ).failure(
      FIRE_SOME_ACTIONS_ON_FAILURE
    )
  }
} 

这只是一个简单的 thunk 异步操作,它以某种方式发出 ajax 请求、返回承诺并在成功或失败时执行其他操作。

所以我们现在遇到的问题是,我希望在更改输入状态时触发这两个操作,但我不能让 onChange 执行两个函数。所以我将创建一个名为 ServerUpdatesMiddleware

的中间件
import _ from 'lodash'
import {
  UPDATE_FIELD,
} from 'actionsPath'

export default ({ dispatch }) => next => action => {
  if(_.includes([UPDATE_FIELD], action.type)){
    switch(action.type){
      case UPDATE_FIELD:
        dispatch(updateFieldOnServer(action.val))
    }
  }
  return next(action)
}

我可以将它添加到我的堆栈中:

import ServerUpdatesMiddleware from 'pathToMe'

const createStoreWithMiddleware = applyMiddleware(
  ServerUpdatesMiddleware,
  thunkMiddleware,
  logger
)(createStore);

并且现在每次 updateField 动作将被调度时,它会自动调度 updateFieldOnServer 动作。

这只是我认为可以轻松描述问题的示例 - 这个问题可以通过许多其他不同的方式解决,但我认为它非常符合要求。这就是我做事的方式——希望对你有所帮助。

我一直在使用中间件,并且有很多中间件 - 这种方法从来没有遇到过任何问题,而且它简化了应用程序逻辑 - 您只需查看一个地方即可了解发生了什么。

【讨论】:

    【解决方案3】:

    使用store.subscribe()

    最简单的方法是简单地使用store.subscribe()方法:

    let prevState
    store.subscribe(() => {
      let state = store.getState()
    
      if (state.something !== prevState.something) {
        store.dispatch(something())
      }
    
      prevState = state
    })
    

    您可以编写一个自定义抽象,让您注册副作用的条件,以便更加声明地表达它们。

    使用 Redux 循环

    您可能想查看Redux Loop,它可以让您描述效果(例如 AJAX)调用一起与您的 reducer 中的状态更新。

    通过这种方式,您可以“返回”这些效果以响应某些操作,就像您当前 return 下一个状态一样:

    export default function reducer(state, action) {
      switch (action.type) {
        case 'LOADING_START':
          return loop(
            { ...state, loading: true },
            Effects.promise(fetchDetails, action.payload.id)
          );
    
        case 'LOADING_SUCCESS':
          return {
            ...state,
            loading: false,
            details: action.payload
          };
    

    这种方法的灵感来自Elm Architecture

    使用 Redux Saga

    您还可以使用Redux Saga,让您编写可以采取操作、执行一些异步工作并放置结果的长时间运行的进程(“sagas”)对商店采取的行动。 Sagas 关注特定的动作而不是状态更新,这不是你所要求的,但我想我还是会提到它们以防万一。它们非常适合复杂的异步控制流和并发。

    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});
       }
    }
    
    function* mySaga() {
      yield* takeEvery("USER_FETCH_REQUESTED", fetchUser);
    }
    

    没有一个真正的方法

    所有这些选项都有不同的权衡。有时人们会使用一两个,甚至全部三个,这取决于测试和描述必要逻辑时最方便的方法。我鼓励您尝试所有这三种方法并选择最适合您的用例的方法。

    【讨论】:

      【解决方案4】:

      componentWillReceiveProps 的 react 生命周期是最好的地方。 componentWillReceiveProps 将同时传递新旧道具,您可以在其中检查更改并调度您的操作,这反过来将触发 ajax 调用。

      但这里要注意的是,您正在检查的状态对象需要通过mapStateToProps 添加为组件的道具,以便将其传递给 componentWillReceiveProps。希望对您有所帮助!

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-01-24
        • 1970-01-01
        • 2019-08-09
        • 1970-01-01
        • 2023-03-19
        • 2021-04-12
        • 2019-12-30
        • 2016-11-28
        相关资源
        最近更新 更多