我正在将现有的状态模型转换为Redux,并且它在大多数情况下都是无痛的。然而,我遇到麻烦的一点是转换“观察”状态ajax请求。基本上,我有一些ajax请求“链接”到其他状态,所以无论谁修改它们,它们总是会被正确发出。我可以通过订阅Redux商店更新获得类似的行为,但是在侦听器中触发操作感觉就像是黑客。
一种可能的解决方案是通过thunk模式将逻辑移动到动作创建器。问题是我要么必须跨操作复制提取逻辑(因为多个操作可以修改“观察”状态),或者将大多数reducer逻辑拉到操作创建者级别。动作创建者也不应该知道减速器将如何响应已发布的动作。
我可以批处理“子动作”,所以我只需要在每个动作“块”中放置适当的提取逻辑,但这似乎违反了产生有效状态的动作的概念。我宁愿在行动创造者层面承担这个责任。
是否有任何普遍接受的规则?这不是一个简单的应用程序,在组件与之交互时进行ad hoc ajax请求,大多数数据在多个组件之间共享,并且请求被优化并获取以响应状态更改。
TLDR; 我想触发ajax请求以响应状态的变化,而不是在特定动作发生时。除了在订阅监听器中触发这些操作之外,是否有更好的“Redux特定”方式来组织action / actionCreators来模拟这种行为?
答案 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 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来编写可以执行操作,执行某些异步工作以及 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)
componentWillReceiveProps
是执行此操作的最佳位置。 componentWillReceiveProps
将传递新旧道具,并且您可以检查更改并发送您的操作,然后启动ajax调用。
但是这里的问题是你要检查的状态对象需要通过mapStateToProps
作为组件的道具添加,以便它被传递给componentWillReceiveProps。希望它有所帮助!