我已经看过各种各样的中间件,比如redux-promise,如果你知道连续的动作是在根源(缺乏一个更好的术语)被触发的行为,这似乎没什么问题 .
从本质上讲,我想维护一个可以随时添加的动作队列 . 每个对象在其状态中具有该队列的实例,并且相关的动作可以相应地排队,处理和出列 . 我有一个实现,但是这样做我在我的动作创建者中访问状态,这感觉就像一个反模式 .
我将尝试给出一些用例和实现的背景信息 .
用例
假设您要创建一些列表并将其保留在服务器上 . 在列表创建时,服务器使用该列表的id进行响应,该ID用于与列表相关的后续API endpoints :
http://my.api.com/v1.0/lists/ // POST returns some id
http://my.api.com/v1.0/lists/<id>/items // API end points include id
想象一下,客户希望对这些API点进行乐观更新,以增强用户体验 - 没有人喜欢看微调器 . 因此,当您创建列表时,会立即显示新列表,并在添加项目时添加选项:
+-------------+----------+
| List Name | Actions |
+-------------+----------+
| My New List | Add Item |
+-------------+----------+
假设有人试图在初始创建调用的响应之前添加一个项目 . items API依赖于id,因此我们知道在拥有该数据之前我们无法调用它 . 但是,我们可能希望乐观地显示新项目并将对项目API的调用排入队列,以便在创建调用完成后触发它 .
一个潜在的解决方案
我目前用来解决这个问题的方法是给每个列表一个动作队列 - 也就是说,将连续触发的Redux动作列表 .
列表创建的reducer功能可能如下所示:
case ADD_LIST:
return {
id: undefined, // To be filled on server response
name: action.payload.name,
actionQueue: []
}
然后,在动作创建者中,我们将行动排队而不是直接触发它:
export const createListItem = (name) => {
return (dispatch) => {
dispatch(addList(name)); // Optimistic action
dispatch(enqueueListAction(name, backendCreateListAction(name));
}
}
为简洁起见,假设backendCreateListAction函数调用一个fetch API,它会在成功/失败时将消息分派到列表中 .
问题
这里让我担心的是enqueueListAction方法的实现 . 这是我访问状态以控制队列进度的地方 . 它看起来像这样(忽略名称上的匹配 - 实际上这实际上使用了clientId,但我试图保持示例简单):
const enqueueListAction = (name, asyncAction) => {
return (dispatch, getState) => {
const state = getState();
dispatch(enqueue(name, asyncAction));{
const thisList = state.lists.find((l) => {
return l.name == name;
});
// If there's nothing in the queue then process immediately
if (thisList.actionQueue.length === 0) {
asyncAction(dispatch);
}
}
}
这里,假设enqueue方法返回一个普通操作,该操作将异步操作插入到列表actionQueue中 .
整个事情感觉有点不利于谷物,但我不确定是否还有另一种方式可以解决它 . 另外,由于我需要在我的asyncActions中调度,我需要将调度方法传递给它们 .
该方法中有类似的代码从列表中出列,如果存在则触发下一个操作:
const dequeueListAction = (name) => {
return (dispatch, getState) => {
dispatch(dequeue(name));
const state = getState();
const thisList = state.lists.find((l) => {
return l.name === name;
});
// Process next action if exists.
if (thisList.actionQueue.length > 0) {
thisList.actionQueue[0].asyncAction(dispatch);
}
}
一般来说,我可以忍受这个,但我担心它是一个反模式,并且可能有一个更简洁,惯用的方式在Redux中这样做 .
任何帮助表示赞赏 .
5 回答
看看这个:https://github.com/gaearon/redux-thunk
单独的id不应该通过reducer . 在您的动作创建者(thunk)中,首先获取列表ID,然后()执行第二次调用以将项目添加到列表中 . 在此之后,您可以根据添加是否成功来分派不同的操作 .
您可以在执行此操作时调度多个操作,以报告服务器交互何时开始和结束 . 这将允许您显示消息或微调器,以防操作繁重并可能需要一段时间 .
可以在此处找到更深入的分析:http://redux.js.org/docs/advanced/AsyncActions.html
所有归功于丹阿布拉莫夫
我有完美的工具,可以满足您的需求 . 当你需要对redux进行大量控制时,(特别是任何异步)并且你需要按顺序执行redux动作,没有比Redux Sagas更好的工具了 . 它 Build 在es6生成器之上,为您提供了很多控制,因为从某种意义上说,您可以在某些点暂停代码 .
您描述的 action queue 是所谓的 saga . 现在,因为它是为了与redux一起工作而创建的,所以可以通过在组件中调度来触发这些传奇 .
由于Sagas使用发电机,您还可以确定您的发货按特定顺序发生,并且仅在某些条件下发生 . 以下是他们的文档中的示例,我将引导您完成它以说明我的意思:
好吧,它起初看起来有点令人困惑,但这个传奇定义了登录序列所需的确切顺序发生 . 由于生成器的性质,允许无限循环 . 当您的代码到达 yield 时,它将停在该行并等待 . 在你告诉它之前,它不会继续下一行 . 所以看看
yield take('LOGIN_REQUEST')
的位置 . 该传奇将在此时产生或等待,直到你发送'LOGIN_REQUEST',之后传奇将调用授权方法,直到下一个收益 . 下一个方法是异步yield call(Api.storeItem, {token})
,因此在代码解析之前它不会转到下一行 .现在,这就是魔术发生的地方 . 该传奇将在
yield take('LOGOUT')
再次停止,直到您在应用程序中发送LOGOUT . 这是至关重要的,因为如果您在LOGOUT之前再次发送LOGIN_REQUEST,则不会调用登录过程 . 现在,如果您发送LOGOUT,它将循环回第一个yield并等待应用程序再次发送LOGIN_REQUEST .到目前为止,Redux Sagas是我最喜欢使用Redux的工具之一 . 它为您提供了对应用程序的大量控制,任何阅读代码的人都会感谢您,因为现在所有内容都会一次读取一行 .
您不必处理排队操作 . 它将隐藏数据流,它将使您的应用程序更加繁琐,无法进行调试 .
我建议您在创建列表或项目时使用一些临时ID,然后在实际从商店收到实际ID时更新这些ID .
这样的事可能吗? (不测试,但你得到的ID):
EDIT :我一开始并不理解保存列表时需要自动保存的项目 . 我编辑了
createList
动作创建者 .这就是我要解决这个问题的方法:
确保每个本地列表都有唯一的标识符 . 我不是在谈论后端ID . 名称可能不足以识别列表?尚未持久化的“乐观”列表应该是唯一可识别的,并且用户可以尝试创建具有相同名称的2个列表,即使它是边缘情况 .
在创建列表时,将后端标识的承诺添加到缓存
在项目添加上,尝试从Redux商店获取后端ID . 如果它不存在,那么尝试从
CreatedListIdCache
获取它 . 返回的id必须是异步的,因为CreatedListIdCache返回一个promise .在
addItem
中使用此方法,以便您的addItem将自动延迟,直到后端ID可用您可能想要清理CreatedListIdPromiseCache,但对于大多数应用程序来说它可能不是很重要,除非您有非常严格的内存使用要求 .
另一种选择是后端id在前端计算,类似于UUID . 你的后端只需要验证这个id的唯一性 . 因此,即使后端尚未回复,您仍然会为所有乐观创建的列表提供有效的后端ID .
我遇到了类似的问题 . 我需要一个队列来保证将乐观操作提交或最终提交(如果出现网络问题),并按照相同的顺序创建远程服务器,如果不可能则回滚 . 我发现仅使用Redux,因为我认为它不是为此而设计的,并且只使用promises这样做可能真的是一个难以理解的问题,除了你需要以某种方式管理你的队列状态这一事实 . .. 恕我直言 .
我认为@Pcriulan 's suggestion on using redux-saga was a good one. At first sight, redux-saga doesn' t提供任何帮助,直到你到channels . 这为您打开了一扇门来处理其他语言所做的其他语言的并发性,特别是CSP(例如,参见Go或Clojure的async),感谢JS生成器 . 甚至有questions为什么以Saga模式命名而不是CSP哈哈...无论如何 .
所以这是一个传奇如何帮助你的队列:
所以这里有趣的部分是通道如何充当缓冲区(队列),它保持“监听”传入的操作,但在完成当前操作之前不会继续执行将来的操作 . 您可能需要查看他们的文档以便更好地掌握代码,但我认为这是值得的 . 重置 Channels 部分可能或不适合您的需求:思考:
希望能帮助到你!