在 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 的教程时,它们似乎并存。
示例代码非常简单,基本上描述了这些步骤:
- 在用户点击时,调用带有一些参数的函数
- 这个函数调用另一个异步函数(比如网络调用)
- 异步调用完成后,触发到另一个页面的路由操作
背景:我想通过将所有 Meteor 特定位移出 React 组件来逐步重构 Meteor 项目,最终用其他东西替换前面和后面的 Meteor。由于大约有 50KLOC,我无法一次完成此操作,因此我逐渐一次通过一条路线进行工作,希望最终得到一个标准的 React+Redux+ReduxRouter 包。在当前的代码路由中,数据获取和渲染在每个组件中都有些交织在一起,我很难找到将多步异步逻辑放在哪里,例如下面的示例。
关于堆栈的详细信息,我正在努力解决问题:
- 用于路由的 FlowRouter
- Meteor/MiniMongo 用于数据突变和检索
- React Komposer 对于高阶组件
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-thunk
、redux-actions
或redux-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.
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 的教程时,它们似乎并存。
示例代码非常简单,基本上描述了这些步骤:
- 在用户点击时,调用带有一些参数的函数
- 这个函数调用另一个异步函数(比如网络调用)
- 异步调用完成后,触发到另一个页面的路由操作
背景:我想通过将所有 Meteor 特定位移出 React 组件来逐步重构 Meteor 项目,最终用其他东西替换前面和后面的 Meteor。由于大约有 50KLOC,我无法一次完成此操作,因此我逐渐一次通过一条路线进行工作,希望最终得到一个标准的 React+Redux+ReduxRouter 包。在当前的代码路由中,数据获取和渲染在每个组件中都有些交织在一起,我很难找到将多步异步逻辑放在哪里,例如下面的示例。
关于堆栈的详细信息,我正在努力解决问题:
- 用于路由的 FlowRouter
- Meteor/MiniMongo 用于数据突变和检索
- React Komposer 对于高阶组件
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-thunk
、redux-actions
或redux-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 在他对
您可能想通读 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.