Redux 中的排队操作

2024-05-04

我目前遇到的情况是我需要连续运行 Redux Actions。我看过各种中间件,比如 redux-promise,看起来不错如果您知道触发根操作(由于缺乏更好的术语)时的连续操作是什么.

本质上,我想维护一个可以随时添加的操作队列。每个对象在其状态下都有一个该队列的实例,并且相关操作可以相应地入队、处理和出队。我有一个实现,但这样做时我正在访问动作创建器中的状态,这感觉像是一种反模式。

我将尝试提供一些有关用例和实现的背景信息。

Use Case

假设您要创建一些列表并将它们保存在服务器上。创建列表时,服务器会使用该列表的 id 进行响应,该 id 用于与该列表相关的后续 API 端点:

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 操作列表。

用于创建列表的化简器功能可能如下所示:

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,该 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 中可能有一种更简洁、更惯用的方法来做到这一点。

任何帮助表示赞赏。


我有满足您需求的完美工具。当您需要对 redux 进行大量控制(尤其是任何异步操作)并且需要按顺序发生 redux 操作时,没有比这更好的工具了终极传奇 https://redux-saga.js.org/。它构建在 es6 生成器之上,为您提供了很多控制权,因为从某种意义上说,您可以在某些点暂停代码。

The 动作队列你所描述的就是所谓的saga。现在,由于它是为与 redux 一起使用而创建的,因此可以通过在组件中分派来触发这些传奇来运行。

由于 Sagas 使用生成器,您还可以确定地确保您的调度按特定顺序发生,并且仅在特定条件下发生。这是他们文档中的一个示例,我将引导您完成它以说明我的意思:

function* loginFlow() {
  while (true) {
    const {user, password} = yield take('LOGIN_REQUEST')
    const token = yield call(authorize, user, password)
    if (token) {
      yield call(Api.storeItem, {token})
      yield take('LOGOUT')
      yield call(Api.clearItem, 'token')
    }
  }
}

好吧,乍一看有点令人困惑,但这个传奇定义了登录序列需要发生的确切顺序。由于生成器的性质,无限循环是允许的。当你的代码到达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 一起使用的工具之一。它为您提供了对应用程序的强大控制权,任何阅读您的代码的人都会感谢您,因为现在所有内容一次只读取一行。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Redux 中的排队操作 的相关文章

随机推荐