在 Redux 中将上下文特定的多步异步逻辑放在哪里

Where to put context specific multi-step async logic in Redux

tl;dr 我想知道在 redux 架构中将上下文特定的多步异步回调逻辑放在何处,以及我是否在正确的轨道上使用示例我在下面提供的代码。 "multi-step" 和 "context specific" 通常是指由某些用户操作(onClicks 等)发起的服务器调用,其中逻辑可能仅与给定组件相关(例如成功时重定向到给定路由)。

关于具有副作用的代码的 redux 文档 has this to say

In general, Redux suggests that code with side effects should be part of the action creation process. While that logic can be performed inside of a UI component, it generally makes sense to extract that logic into a reusable function so that the same logic can be called from multiple places—in other words, an action creator function.

虽然这看起来不错,但我不完全确定是否 "correct" 将对我的路由组件的调用放在那里,因为这些动作创建者通常看起来很通用,并触发路由到某些其他资源该应用程序通常非常依赖上下文。

我还发现将这些完全不同的野兽放在与 "clean" 同步相同的文件 (foo-model/actions.js) 中有点奇怪,它们异步触发动作创建者并分派结果动作行动创造者。这是正确的地方吗?在阅读有关 Redux 的教程时,它们似乎并存。

示例代码非常简单,基本上描述了这些步骤:

  1. 在用户点击时,调用带有一些参数的函数
  2. 这个函数调用另一个异步函数(比如网络调用)
  3. 异步调用完成后,触发到另一个页面的路由操作

背景:我想通过将所有 Meteor 特定位移出 React 组件来逐步重构 Meteor 项目,最终用其他东西替换前面和后面的 Meteor。由于大约有 50KLOC,我无法一次完成此操作,因此我逐渐一次通过一条路线进行工作,希望最终得到一个标准的 React+Redux+ReduxRouter 包。在当前的代码路由中,数据获取和渲染在每个组件中都有些交织在一起,我很难找到将多步异步逻辑放在哪里,例如下面的示例。

关于堆栈的详细信息,我正在努力解决问题:

MyContainerComponent 中的旧 Meteor 代码

// triggered as onClick={(e) => this.saveEncounter(e.target.value)}
// in render()
const saveEncounter = (encounter) => {
    Meteor.call('createEncounter', encounter, handleSaveResult);
  }
};

const handleSaveResult = (err, encounterId) => {
  if (err) {
    this.setState({errorMessages: err});
  } else {
    // route to another page
    NavigationActions.goTo('encounter', {encounterId: this.props.encounter._id || encounterId});
  }
}

新的 redux 代码 - 移入 actions.js

在这一点上,我试图让实施保持直截了当(没有额外的部门)以了解基础。 "Simplification"使用redux-thunkredux-actionsredux-saga需要稍后来。仿照 example code in the Redux tutorial for Async Actions

export const saveEncounter = (encounter) => {

    function handleSave(err, encounterId) {
        if (err) {
            dispatch(createEncounterFailure(err), encounter);
        } else {
            dispatch(createEncounterSuccess(encounterId));
        }
    }

    dispatch(createEncounterRequest(encounter));
    Meteor.call('createEncounter', encounter, handleSave);
}


// simple sync actions creators
export const CREATE_ENCOUNTER_REQUEST = 'CREATE_ENCOUNTER_REQUEST';
function createEncounterRequest(encounter) {
    return {
        type: CREATE_ENCOUNTER_REQUEST,
        encounter
    };
}
export const CREATE_ENCOUNTER_FAILURE = 'CREATE_ENCOUNTER_FAILURE';
function createEncounterFailure(error, encounter) {
    return {
        type: CREATE_ENCOUNTER_FAILURE,
        error,
        encounter
    };
}
export const CREATE_ENCOUNTER_SUCCESS = 'CREATE_ENCOUNTER_SUCCESS';
function createEncounterSuccess(encounterId) {
    return {
        type: CREATE_ENCOUNTER_SUCCESS,
        encounterId
    };
} 

根据我使用 Redux 的经验,我没有发现将异步调用放入 action creator 有任何问题。我认为 redux-thunk 或其他一些中间件非常有用,即使对于简单的设置也是如此。

我唯一要补充的是,我发现您的示例代码可读性不强。

就我个人而言,我开始喜欢 the ducks pattern,但也只是将动作类型、动作创建者和缩减器保存在单独的文件中会有助于提高清晰度。

希望对您有所帮助。

我明白你的意思,你想有一种方法来划分和分类你的行为,是吗?将执行同步代码、异步代码、记录器等的操作

就个人而言,我使用一些命名约定。如果我必须分派一个必须获取一些数据的操作,我将其称为 REQUEST_DATA。如果必须存储一些从服务器到达的数据到ReduxStore,我称之为STORE_DATA

我没有特定的模式。我还必须指出,我根据功能划分我的代码库,所以我定义我的动作的模块非常小而整洁

正如您在评论中指出的那样,Dan Abramov 在他对 . He also wrote another excellent answer in .

的回答中讨论了在 Redux 中处理异步工作背后的许多想法。

您可能想通读 Redux Side Effects category of my React/Redux links list 中的其他一些文章,以更好地了解在 Redux 中处理异步逻辑的方法。

一般来说,听起来您可能想要使用 "sagas" 或 "observables" 来管理您的一些异步逻辑和工作流。用于异步行为的 Redux 中间件种类繁多 - 我在我的博客中总结了主要类别和最受欢迎的库 post The Tao of Redux, Part 2 - Practice and Philosophy. There's also some interesting thoughts on a very decoupled saga-based Redux architecture in a post called Redux Saga in Action.