如何使用Redux来响应状态更改来触发AJAX调用?

时间:2016-02-08 05:10:13

标签: ajax asynchronous architecture state redux

我正在将现有的状态模型转换为Redux,并且它在大多数情况下都是无痛的。然而,我遇到麻烦的一点是转换“观察”状态ajax请求。基本上,我有一些ajax请求“链接”到其他状态,所以无论谁修改它们,它们总是会被正确发出。我可以通过订阅Redux商店更新获得类似的行为,但是在侦听器中触发操作感觉就像是黑客。

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

我可以批处理“子动作”,所以我只需要在每个动作“块”中放置适当的提取逻辑,但这似乎违反了产生有效状态的动作的概念。我宁愿在行动创造者层面承担这个责任。

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

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

4 个答案:

答案 0 :(得分:20)

使用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)与一起调用

通过这种方式,您可以“返回”这些效果,以响应您当前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来编写可以执行操作,执行某些异步工作以及 put 结果对商店的行动。 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);
}

没有一个真正的方式

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

答案 1 :(得分:2)

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

假设我有一个本地行动:

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

输入字段为:

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

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

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

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

这只是一个简单的thunk async动作,它以某种方式发出ajax请求,返回promises并在成功或失败时做其他事情。

所以我们现在遇到的问题是,当我改变输入状态时我想要触发这两个动作,但是我不能让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 操作。

这只是我认为可以轻松描述问题的例子 - 这个问题可以通过许多其他方式解决,但我认为它很符合要求。这就是我做事的方式 - 希望它能帮助你。

我一直在使用中间件并且有许多中间件 - 这种方法从来没有任何问题,它简化了应用程序逻辑 - 你只需要在一个地方查找最新情况。

答案 2 :(得分:0)

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

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

在我看来,这是一种比异步动作创建者更实用的方法,你可能喜欢或不喜欢它!

答案 3 :(得分:0)

反应生命周期的{p> componentWillReceiveProps是执行此操作的最佳位置。 componentWillReceiveProps将传递新旧道具,并且您可以检查更改并发送您的操作,然后启动ajax调用。

但是这里的问题是你要检查的状态对象需要通过mapStateToProps作为组件的道具添加,以便它被传递给componentWillReceiveProps。希望它有所帮助!