为什么我们需要Redux中异步流的中间件?

根据文档,“没有中间件,Redux存储只支持同步数据流”。我不明白为什么会这样。为什么容器组件不能调用async API,然后dispatch操作?

例如,想象一个简单的UI:一个字段和一个按钮。当用户按下按钮时,该字段将填充来自远程服务器的数据。

一个字段和一个按钮

import * as React from 'react';import * as Redux from 'redux';import { Provider, connect } from 'react-redux';
const ActionTypes = {STARTED_UPDATING: 'STARTED_UPDATING',UPDATED: 'UPDATED'};
class AsyncApi {static getFieldValue() {const promise = new Promise((resolve) => {setTimeout(() => {resolve(Math.floor(Math.random() * 100));}, 1000);});return promise;}}
class App extends React.Component {render() {return (<div><input value={this.props.field}/><button disabled={this.props.isWaiting} onClick={this.props.update}>Fetch</button>{this.props.isWaiting && <div>Waiting...</div>}</div>);}}App.propTypes = {dispatch: React.PropTypes.func,field: React.PropTypes.any,isWaiting: React.PropTypes.bool};
const reducer = (state = { field: 'No data', isWaiting: false }, action) => {switch (action.type) {case ActionTypes.STARTED_UPDATING:return { ...state, isWaiting: true };case ActionTypes.UPDATED:return { ...state, isWaiting: false, field: action.payload };default:return state;}};const store = Redux.createStore(reducer);const ConnectedApp = connect((state) => {return { ...state };},(dispatch) => {return {update: () => {dispatch({type: ActionTypes.STARTED_UPDATING});AsyncApi.getFieldValue().then(result => dispatch({type: ActionTypes.UPDATED,payload: result}));}};})(App);export default class extends React.Component {render() {return <Provider store={store}><ConnectedApp/></Provider>;}}

当呈现导出的组件时,我可以单击按钮并正确更新输入。

请注意connect调用中的update函数。它分派一个操作,告诉应用程序它正在更新,然后执行异步调用。调用完成后,提供的值作为另一个操作的有效负载被分派。

这种方法有什么问题?为什么我要像留档建议的那样使用Redux Thunk或Redux Promise?

编辑:我在Redux仓库里搜了一下线索,发现以前要求Action Creators是纯函数。比如这是一个用户试图为异步数据流提供更好的解释:

动作创建者本身仍然是一个纯函数,但它返回的thunk函数不需要是,它可以执行我们的异步调用

动作创作者不再需要纯粹。所以,过去肯定需要thunk/Promise中间件,但现在似乎不再是这样了?

207872 次浏览

简短的回答:对我来说,这似乎是解决异步问题的一个完全合理的方法。

在我们刚刚开始工作的一个新项目中,我有一个非常相似的思路。我是vanilla Redux优雅系统的忠实粉丝,该系统以一种远离React组件树内部的方式更新存储和重新渲染组件。对我来说,连接到优雅的dispatch机制来处理异步似乎很奇怪。

我最终采用了一种非常相似的方法,与我从我们的项目中分解出来的库中的方法非常相似,我们称之为React-red ux-控制器

我最终没有采用你上面的确切方法,原因有几个:

  1. 按照你的编写方式,那些调度函数无权访问商店。你可以通过让你的UI组件传递调度函数需要的所有信息来解决这个问题。但我认为这会不必要地将这些UI组件与调度逻辑耦合起来。更有问题的是,调度函数没有明显的方法来访问异步延续中的更新状态。
  2. 调度函数可以通过词法作用域访问dispatch本身。这限制了一旦connect语句失控时重构的选项-并且仅使用一个update方法看起来非常笨拙。因此,如果您将这些调度器函数分解为单独的模块,您需要一些系统来让您组合这些调度器函数。

综合起来,您必须构建一些系统,以允许将dispatch和存储以及事件的参数注入到您的调度函数中。我知道这种依赖注入有三种合理的方法:

  • redux-thunk通过将它们传递给您的thunks(根据dome定义,使它们根本不完全是thunks)以功能方式完成此操作。我没有使用其他dispatch中间件方法,但我假设它们基本相同。
  • React-redux控制器通过协程执行此操作。作为奖励,它还允许您访问“选择器”,这是您可能作为connect的第一个参数传入的函数,而不必直接使用原始的标准化存储。
  • 您还可以通过各种可能的机制将它们注入this上下文,从而以面向对象的方式完成。

更新

在我看来,这个难题的一部分是React-Redux的限制。connect的第一个参数获得状态快照,但不是调度。第二个参数获得调度,但不是状态。两个参数都没有获得关闭当前状态的thunk,因为能够在继续/回调时看到更新的状态。

回答开头提出的问题:

为什么容器组件不能调用async API,然后调度操作?

请记住,这些文档适用于Redux,而不是Redux plus React。Redux商店连接到React组件可以完全按照你说的做,但是没有中间件的Plain Jane Redux商店不接受dispatch的参数,除了普通ol对象。

没有中间件,你当然可以做

const store = createStore(reducer);MyAPI.doThing().then(resp => store.dispatch(...));

但这是一个类似的情况,异步被包装周围 Redux而不是处理 Redux。因此,中间件通过修改可以直接传递给dispatch的内容来允许异步。


也就是说,我认为你的建议的精神是有效的。当然还有其他方法可以在Redux+React应用程序中处理异步。

使用中间件的一个好处是,您可以继续像往常一样使用操作创建者,而不必担心它们究竟是如何连接的。例如,使用redux-thunk,您编写的代码看起来很像

function updateThing() {return dispatch => {dispatch({type: ActionTypes.STARTED_UPDATING});AsyncApi.getFieldValue().then(result => dispatch({type: ActionTypes.UPDATED,payload: result}));}}
const ConnectedApp = connect((state) => { ...state },{ update: updateThing })(App);

它看起来与原始版本并没有什么不同-它只是稍微打乱了一下-并且connect不知道updateThing是(或需要)异步的。

如果你也想支持承诺可观察sagas疯狂的习俗高度陈述性的动作创建者,那么Redux可以通过将你传递的内容更改为dispatch(也就是你从动作创建者那里返回的内容)来做到这一点。不需要破坏React组件(或connect调用)。

这种方法有什么问题?为什么我要像留档建议的那样使用Redux Thunk或Redux Promise?

这种方法没有问题。只是在大型应用程序中不方便,因为你将有不同的组件执行相同的操作,你可能想要防抖一些操作,或者保持一些本地状态,如自动递增ID靠近操作创建者等。因此,从维护的角度来看,将操作创建者提取到单独的功能中更容易。

您可以阅读我对"如何在超时情况下调度Redux操作"的回答以获得更详细的走查。

像Redux Thunk或Redux Promise这样的中间件只是给你“语法糖”来调度thunks或Promise,但你不会必须使用它。

因此,如果没有任何中间件,您的动作创建者可能看起来像

// action creatorfunction loadData(dispatch, userId) { // needs to dispatch, so it is first argumentreturn fetch(`http://data.com/${userId}`).then(res => res.json()).then(data => dispatch({ type: 'LOAD_DATA_SUCCESS', data }),err => dispatch({ type: 'LOAD_DATA_FAILURE', err }));}
// componentcomponentWillMount() {loadData(this.props.dispatch, this.props.userId); // don't forget to pass dispatch}

但是使用Thunk Middleware,你可以这样写:

// action creatorfunction loadData(userId) {return dispatch => fetch(`http://data.com/${userId}`) // Redux Thunk handles these.then(res => res.json()).then(data => dispatch({ type: 'LOAD_DATA_SUCCESS', data }),err => dispatch({ type: 'LOAD_DATA_FAILURE', err }));}
// componentcomponentWillMount() {this.props.dispatch(loadData(this.props.userId)); // dispatch like you usually do}

所以没有太大的区别。我喜欢后一种方法的一件事是组件不在乎动作创建者是异步的。它只是正常调用dispatch,它也可以使用mapDispatchToProps用简短的语法绑定这样的动作创建者,等等。组件不知道动作创建者是如何实现的,你可以在不同的异步方法(Redux Thunk、Redux Promise、Redux Saga)之间切换,而无需更改组件。另一方面,使用前一种显式方法,你的组件知道完全特定的调用是异步的,并且需要dispatch通过某种约定传递(例如,作为同步参数)。

还要考虑这段代码将如何变化。假设我们想要第二个数据加载功能,并将它们组合在一个操作创建者中。

对于第一种方法,我们需要注意我们正在调用什么样的动作创造者:

// action creatorsfunction loadSomeData(dispatch, userId) {return fetch(`http://data.com/${userId}`).then(res => res.json()).then(data => dispatch({ type: 'LOAD_SOME_DATA_SUCCESS', data }),err => dispatch({ type: 'LOAD_SOME_DATA_FAILURE', err }));}function loadOtherData(dispatch, userId) {return fetch(`http://data.com/${userId}`).then(res => res.json()).then(data => dispatch({ type: 'LOAD_OTHER_DATA_SUCCESS', data }),err => dispatch({ type: 'LOAD_OTHER_DATA_FAILURE', err }));}function loadAllData(dispatch, userId) {return Promise.all(loadSomeData(dispatch, userId), // pass dispatch first: it's asyncloadOtherData(dispatch, userId) // pass dispatch first: it's async);}

// componentcomponentWillMount() {loadAllData(this.props.dispatch, this.props.userId); // pass dispatch first}

使用Redux Thunk,动作创建者可以dispatch其他动作创建者的结果,甚至不考虑它们是同步的还是异步的:

// action creatorsfunction loadSomeData(userId) {return dispatch => fetch(`http://data.com/${userId}`).then(res => res.json()).then(data => dispatch({ type: 'LOAD_SOME_DATA_SUCCESS', data }),err => dispatch({ type: 'LOAD_SOME_DATA_FAILURE', err }));}function loadOtherData(userId) {return dispatch => fetch(`http://data.com/${userId}`).then(res => res.json()).then(data => dispatch({ type: 'LOAD_OTHER_DATA_SUCCESS', data }),err => dispatch({ type: 'LOAD_OTHER_DATA_FAILURE', err }));}function loadAllData(userId) {return dispatch => Promise.all(dispatch(loadSomeData(userId)), // just dispatch normally!dispatch(loadOtherData(userId)) // just dispatch normally!);}

// componentcomponentWillMount() {this.props.dispatch(loadAllData(this.props.userId)); // just dispatch normally!}

使用这种方法,如果您以后希望您的操作创建者查看当前的Redux状态,您可以只使用传递给thunks的第二个getState参数,而无需修改调用代码:

function loadSomeData(userId) {// Thanks to Redux Thunk I can use getState() here without changing callersreturn (dispatch, getState) => {if (getState().data[userId].isLoaded) {return Promise.resolve();}
fetch(`http://data.com/${userId}`).then(res => res.json()).then(data => dispatch({ type: 'LOAD_SOME_DATA_SUCCESS', data }),err => dispatch({ type: 'LOAD_SOME_DATA_FAILURE', err }));}}

如果您需要将其更改为同步,您也可以在不更改任何调用代码的情况下执行此操作:

// I can change it to be a regular action creator without touching callersfunction loadSomeData(userId) {return {type: 'LOAD_SOME_DATA_SUCCESS',data: localStorage.getItem('my-data')}}

因此,使用Redux Thunk或Redux Promise等中间件的好处是组件不知道操作创建者是如何实现的,它们是否关心Redux状态,它们是同步还是异步,以及它们是否调用其他操作创建者。缺点是有点间接,但我们相信在实际应用中这是值得的。

最后,Redux Thunk和朋友只是Redux应用程序中异步请求的一种可能方法。另一种有趣的方法是Redux Saga,它允许你定义长时间运行的守护进程(“sagas”),这些守护进程在来的时候采取行动,并在输出行动之前转换或执行请求。这将逻辑从行动创建者转移到sagas。你可能想查看一下,然后选择最适合你的。

我在Redux存储库中搜索线索,发现过去要求Action Creators是纯函数。

这是不正确的。文档是这样说的,但文档是错误的。
动作创造者从来没有被要求是纯函数。
我们修改了文档以反映这一点。

你不需要。

但是…你应该使用Redux-saga:)

丹·阿布拉莫夫的答案是正确的关于redux-thunk,但我将更多地谈论redux-saga,它非常相似,但更强大。

命令式VS宣告式

  • DOM:jQuery是命令式的/React是声明式的
  • 单子:IO是命令式的/Free是声明式的
  • Redux特效redux-thunk是命令式的/redux-saga是声明式的

当你手中有一个thunk时,比如IO monad或Promise,你很难知道执行后它会做什么。测试thunk的唯一方法是执行它,并模拟调度程序(如果它与更多东西交互,则模拟整个外部世界…)。

如果您使用的是模拟,那么您就不是在进行函数式编程。

从副作用的角度来看,模拟是你的代码不纯洁的标志,在函数式程序员看来,证明有问题。与其下载一个库来帮助我们检查冰山是否完好无损,我们应该绕过它。一个铁杆TDD/Java曾经问我如何在Clojure中进行嘲笑。答案是,我们通常不会。我们通常将其视为需要重构代码的标志。

来源

sagas(因为它们在redux-saga中实现)是声明性的,并且像Free monad或React组件一样,它们更容易在没有任何模拟的情况下进行测试。

看看这个文章

在现代FP中,我们不应该编写程序-我们应该编写程序的描述,然后我们可以随意内省,转换和解释。

(实际上,Redux-saga就像一个混合体:流程是命令式的,但效果是声明性的)

混乱:动作/事件/命令…

在前端世界中,一些后端概念,如CQRS/事件源和Flux/Redux,可能是如何相关的,这有很多困惑,主要是因为在Flux中,我们使用术语“操作”,有时可以表示命令式代码(LOAD_USER)和事件(USER_LOADED)。我相信就像事件源一样,你应该只调度事件。

在实践中使用传说

想象一个带有指向用户配置文件链接的应用程序。使用每个中间件处理此问题的惯用方法是:

redux-thunk

<div onClick={e => dispatch(actions.loadUserProfile(123)}>Robert</div>
function loadUserProfile(userId) {return dispatch => fetch(`http://data.com/${userId}`).then(res => res.json()).then(data => dispatch({ type: 'USER_PROFILE_LOADED', data }),err => dispatch({ type: 'USER_PROFILE_LOAD_FAILED', err }));}

redux-saga

<div onClick={e => dispatch({ type: 'USER_NAME_CLICKED', payload: 123 })}>Robert</div>

function* loadUserProfileOnNameClick() {yield* takeLatest("USER_NAME_CLICKED", fetchUser);}
function* fetchUser(action) {try {const userProfile = yield fetch(`http://data.com/${action.payload.userId }`)yield put({ type: 'USER_PROFILE_LOADED', userProfile })}catch(err) {yield put({ type: 'USER_PROFILE_LOAD_FAILED', err })}}

这个saga翻译为:

每次单击用户名时,获取用户配置文件,然后使用加载的配置文件调度事件。

如您所见,redux-saga有一些优点。

使用takeLatest可以表示您只对获取上次点击的用户名的数据感兴趣(如果用户快速点击很多用户名,则处理并发问题)。这种东西很难用thunks。如果您不想要这种行为,您可以使用takeEvery

请注意,保留actionCreators(在sagasput和组件dispatch中)仍然很有用,因为它可能会帮助您将来添加操作验证(断言/流/打字稿)。

你的代码变得更加可测试,因为效果是声明性的

你不再需要触发像actions.loadUser()这样的类似rpc的调用。你的用户界面只需要调度发生的事情。我们只触发事件(总是过去式!)而不再是操作。这意味着你可以创建解耦的“鸭子”有界上下文,并且saga可以作为这些模块化组件之间的耦合点。

这意味着您的视图更容易管理,因为它们不再需要包含已经发生的事情和应该发生的事情之间的转换层作为效果

例如,想象一个无限滚动视图。CONTAINER_SCROLLED可以导致NEXT_PAGE_LOADED,但是可滚动容器真的有责任决定我们是否应该加载另一个页面吗?然后他必须意识到更复杂的事情,比如最后一页是否成功加载,或者是否已经有一个页面试图加载,或者是否没有更多的项目可以加载?我不这么认为:为了最大的可重用性,可滚动容器应该描述它已经滚动了。页面的加载是该滚动的“业务效应”

有些人可能会争辩说,生成器本质上可以使用局部变量隐藏redux存储之外的状态,但是如果你开始通过启动计时器等在thunks内部编排复杂的东西,你无论如何都会遇到同样的问题。

Sagas可以时间旅行,还支持当前正在开发的复杂流日志记录和开发工具。这是一些已经实现的简单异步流日志记录:

saga流记录

解耦

Sagas不仅取代了redux thunks。它们来自后端/分布式系统/事件源。

这是一个很常见的误解,认为saga只是用更好的可测试性取代你的redux thunks。实际上这只是redux saga的一个实现细节。使用声明式效果比thunks的可测试性更好,但是saga模式可以在命令式或声明式代码之上实现。

首先,saga是一个允许协调长时间运行的事务(最终一致性)和跨不同有界上下文的事务(领域驱动的设计行话)的软件。

为了简化前端世界,假设有widget1和widget2。当单击widget1上的某个按钮时,它应该对widget2产生影响。而不是将两个小部件耦合在一起(即widget1调度一个针对widget2的操作),widget1仅调度其按钮被单击。然后saga监听此按钮单击,然后通过分配widget2知道的新事件来更新widget2。

这增加了一个对于简单应用程序来说不必要的间接级别,但使扩展复杂的应用程序变得更容易。你现在可以将widget1和widget2发布到不同的npm存储库,这样它们就不必知道彼此,而无需它们共享一个全局操作注册表。这2个小部件现在是可以单独存在的有界上下文。它们不需要彼此一致,也可以在其他应用程序中重用。saga是两个小部件之间的耦合点,可以对你的业务以有意义的方式协调它们。

一些关于如何构建Redux应用程序的好文章,您可以在其中使用Redux-saga进行解耦:

一个具体的用途:通知系统

我希望我的组件能够触发应用内通知的显示。但我不希望我的组件与具有自己业务规则的通知系统高度耦合(最多同时显示3个通知、通知队列、4秒显示时间等…)。

我不希望我的JSX组件决定何时显示/隐藏通知。我只是让它能够请求通知,并将复杂的规则留在saga中。这种东西很难用thunks或Promise实现。

通知

我已经描述了这里如何用saga完成

为什么它被称为传奇?

术语saga来自后端世界。我最初在长时间的讨论中向Yassine(Redux-saga的作者)介绍了该术语。

最初,这个术语是用论文引入的,saga模式应该用于处理分布式事务中的最终一致性,但它的用法已被后端开发人员扩展到更广泛的定义,因此它现在也涵盖了“进程管理器”模式(不知何故,最初的saga模式是进程管理器的一种特殊形式)。

今天,术语“saga”令人困惑,因为它可以描述两种不同的东西。正如它在redux-saga中使用的那样,它不描述处理分布式事务的方法,而是一种协调应用程序中操作的方法。redux-saga也可以被称为redux-process-manager

另见:

替代品

如果你不喜欢使用生成器的想法,但你对saga模式及其解耦属性感兴趣,你也可以使用可观察还原实现同样的目的,它使用名称epic来描述完全相同的模式,但使用RxJS。如果你已经熟悉Rx,你会有宾至如归的感觉。

const loadUserProfileOnNameClickEpic = action$ =>action$.ofType('USER_NAME_CLICKED').switchMap(action =>Observable.ajax(`http://data.com/${action.payload.userId}`).map(userProfile => ({type: 'USER_PROFILE_LOADED',userProfile})).catch(err => Observable.of({type: 'USER_PROFILE_LOAD_FAILED',err})));

一些redux-saga有用的资源

2017年建议

  • 不要为了使用它而过度使用Redux-saga。仅可测试的API调用不值得。
  • 对于大多数简单的情况,不要从项目中删除thunks。
  • 如果有意义,请不要犹豫在yield put(someActionThunk)中发送thunks。

如果你害怕使用Redux-saga(或Redux-observable),但只需要解耦模式,请选中red ux-调度-订阅:它允许侦听调度并在侦听器中触发新的调度。

const unsubscribe = store.addDispatchListener(action => {if (action.type === 'ping') {store.dispatch({ type: 'pong' });}});

阿布拉莫夫的目标-每个人的理想-只是将复杂性(和异步调用)封装在最合适和可重用的地方

在标准Redux数据流中,最好在哪里执行此操作?如何:

  • 减速机?不可能。它们应该是没有副作用的纯功能。更新商店是严肃而复杂的业务。不要污染它。
  • 哑视图组件?绝对不是。他们有一个关注点:演示文稿和用户交互,并且应该尽可能简单。
  • 容器组件?可能,但不是最佳的。容器是一个我们封装一些与视图相关的复杂性并与存储交互的地方,这是有道理的,但是:
    • 容器确实需要比哑组件更复杂,但它仍然是一个单一的责任:提供视图和状态/存储之间的绑定。你的异步逻辑与此完全不同。
    • 通过将其放置在容器中,你将把你的异步逻辑锁定在一个上下文中,并耦合到一个或多个视图/路由。坏主意。理想情况下,它都是可重用的,并且与视图完全解耦。
    • (像所有规则一样,如果你有状态绑定逻辑,碰巧可以跨多个上下文重用,或者如果你可以以某种方式将所有状态泛化为集成的GraphQL模式,则可能会有例外。好吧,这可能很酷。但是……大多数时候绑定似乎最终都非常上下文/视图特定。)
  • 其他服务模块?坏主意:你需要注入对商店的访问权限,这是一个可运维性/可测试性的噩梦。最好使用Redux的粒度并仅使用提供的API/模型访问商店。
  • 动作和解释它们的中间件?为什么不呢?!对于初学者来说,这是我们剩下的唯一主要选择。:-)从逻辑上讲,操作系统是解耦的执行逻辑,你可以在任何地方使用。它可以访问商店,可以调度更多的操作。它只有一个责任,那就是组织应用程序周围的控制和数据流,大多数异步都适合这一点。
    • 动作创造者呢?为什么不直接在那里做异步,而不是在动作本身和中间件中?
      • 首先也是最重要的,创建者没有访问存储的权限,就像中间件一样。这意味着你不能调度新的或有操作,不能从存储中读取来编写你的异步,等等。
      • 所以,把复杂性放在必要的复杂的地方,把其他一切都保持简单。创造者可以是简单的,相对纯粹的函数,易于测试。

OK,让我们先看看中间件是如何工作的,这很好地回答了这个问题,这是Redux中pplyMiddleware函数的源代码:

function applyMiddleware() {for (var _len = arguments.length, middlewares = Array(_len), _key = 0; _key < _len; _key++) {middlewares[_key] = arguments[_key];}
return function (createStore) {return function (reducer, preloadedState, enhancer) {var store = createStore(reducer, preloadedState, enhancer);var _dispatch = store.dispatch;var chain = [];
var middlewareAPI = {getState: store.getState,dispatch: function dispatch(action) {return _dispatch(action);}};chain = middlewares.map(function (middleware) {return middleware(middlewareAPI);});_dispatch = compose.apply(undefined, chain)(store.dispatch);
return _extends({}, store, {dispatch: _dispatch});};};}

看看这个部分,看看我们的派遣如何成为函数

  ...getState: store.getState,dispatch: function dispatch(action) {return _dispatch(action);}
  • 请注意,每个中间件将被赋予dispatchgetState函数作为命名参数。

好的,这就是Redux-thunk作为Redux最常用的中间件之一的介绍:

Redux Thunk中间件允许您编写返回的动作创建者一个函数而不是一个动作。thank可用于延迟动作的调度,或仅在特定条件下调度满足。内部函数接收存储方法调度和##获取状态参数

因此,如您所见,它将返回一个函数而不是一个操作,这意味着您可以等待并随时调用它,因为它是一个函数…

Thunk到底是什么?维基百科是这样介绍的:

在计算机编程中,thunk是用于注入额外的计算到另一个子程序中。Thunks主要是用于将计算延迟到需要时,或用于插入在另一个子程序的开头或结尾进行操作。他们有各种其他应用程序来编译代码生成和在模块化编程。

这个词起源于“思考”的滑稽衍生物。

thunk是一个函数,它包装表达式以延迟其评价。

//calculation of 1 + 2 is immediate//x === 3let x = 1 + 2;
//calculation of 1 + 2 is delayed//foo can be called later to perform the calculation//foo is a thunk!let foo = () => 1 + 2;

因此,看看这个概念是多么简单,以及它如何帮助您管理异步操作…

这是你可以没有它生活的东西,但请记住,在编程中,总有更好,更整洁,更正确的方法来做事。

应用中间件Redux

使用Redux-saga是React-redux实现中最好的中间件。

例如:store.js

  import createSagaMiddleware from 'redux-saga';import { createStore, applyMiddleware } from 'redux';import allReducer from '../reducer/allReducer';import rootSaga from '../saga';
const sagaMiddleware = createSagaMiddleware();const store = createStore(allReducer,applyMiddleware(sagaMiddleware))
sagaMiddleware.run(rootSaga);
export default store;

然后saga.js

import {takeLatest,delay} from 'redux-saga';import {call, put, take, select} from 'redux-saga/effects';import { push } from 'react-router-redux';import data from './data.json';
export function* updateLesson(){try{yield put({type:'INITIAL_DATA',payload:data}) // initial data from jsonyield* takeLatest('UPDATE_DETAIL',updateDetail) // listen to your action.js}catch(e){console.log("error",e)}}
export function* updateDetail(action) {try{//To write store update details}catch(e){console.log("error",e)}}
export default function* rootSaga(){yield [updateLesson()]}

然后action.js

 export default function updateFruit(props,fruit) {return ({type:"UPDATE_DETAIL",payload:fruit,props:props})}

然后reducer.js

import {combineReducers} from 'redux';
const fetchInitialData = (state=[],action) => {switch(action.type){case "INITIAL_DATA":return ({type:action.type, payload:action.payload});break;}return state;}const updateDetailsData = (state=[],action) => {switch(action.type){case "INITIAL_DATA":return ({type:action.type, payload:action.payload});break;}return state;}const allReducers =combineReducers({data:fetchInitialData,updateDetailsData})export default allReducers;

然后main.js

import React from 'react';import ReactDOM from 'react-dom';import App from './app/components/App.jsx';import {Provider} from 'react-redux';import store from './app/store';import createRoutes from './app/routes';
const initialState = {};const store = configureStore(initialState, browserHistory);
ReactDOM.render(<Provider store={store}><App />  /*is your Component*/</Provider>,document.getElementById('app'));

试试这个…正在工作

有同步动作创建者,也有异步动作创建者。

同步动作创建者是当我们调用它时,它立即返回一个Action对象,其中包含附加到该对象的所有相关数据,并准备好由我们的简化程序处理。

异步动作创建者是一种在准备好最终分派动作之前需要一点时间的动作创建者。

根据定义,每当您有一个动作创建者发出网络请求时,它总是有资格成为异步动作创建者。

如果你想在Redux应用程序中拥有异步操作创建者,你必须安装一个称为中间件的东西,它将允许你处理这些异步操作创建者。

您可以在错误消息中验证这一点,该消息告诉我们使用自定义中间件进行异步操作。

那么什么是中间件,为什么我们需要它来实现Redux中的异步流呢?

在redux中间件(如redux-thunk)的上下文中,中间件帮助我们处理异步操作创建者,因为这是Redux无法开箱即用的东西。

随着中间件集成到Redux循环中,我们仍然调用动作创建者,这将返回一个将被调度的动作,但是现在当我们调度一个动作时,而不是直接将它发送到我们所有的减速器,我们将说一个动作将通过应用程序内的所有不同中间件发送。

在单个Redux应用程序中,我们可以拥有任意数量或任意数量的中间件。在大多数情况下,在我们工作的项目中,我们将有一两个中间件连接到我们的Redux商店。

中间件是一个简单的JavaScript函数,我们调度的每一个操作都会调用它。在该函数内部,中间件有机会阻止将操作调度到任何减速器,它可以修改操作或以任何方式乱搞操作,例如,我们可以创建一个中间件,控制台记录您调度的每一个操作,只是为了您的观看乐趣。

您可以将大量开源中间件作为依赖项安装到您的项目中。

您不仅限于使用开源中间件或将它们安装为依赖项。您可以编写自己的自定义中间件并在Redux商店中使用它。

中间件最流行的用途之一(并得到你的答案)是处理异步动作创建者,可能最流行的中间件是redux-thunk,它是关于帮助你处理异步动作创建者。

还有许多其他类型的中间件也可以帮助您处理异步操作创建者。

回答这个问题:

为什么容器组件不能调用async API,然后#36825;行动?

我认为至少有两个原因:

第一个原因是关注点的分离,调用api并取回数据不是action creator的工作,你必须将两个参数传递给你的action creator functionaction typepayload

第二个原因是因为redux store正在等待具有强制操作类型和可选的payload的普通对象(但在这里您也必须传递有效负载)。

动作创建者应该是一个普通对象,如下所示:

function addTodo(text) {return {type: ADD_TODO,text}}

而作业Redux-Thunk midlewaredispache的结果是你的api call到相应的action

在企业项目中工作时,有许多中间件中可用的需求,例如(saga)在简单的异步流中不可用,下面是一些:

  • 并行中运行请求
  • 拉动未来的行动,无需等待
  • 非阻塞通话种族特效,示例拾取优先
  • 响应启动流程对您的任务进行排序(第一次调用中的第一个)
  • 作曲
  • 任务取消动态分叉任务。
  • 支持Redux中间件之外的并发运行Saga。
  • 使用渠道

列表很长,只需查看佐贺留档中的高级部分

Redux不能返回函数而不是操作。这只是一个事实。这就是人们使用Thunk的原因。阅读这14行代码,看看它如何允许异步循环与一些添加的函数分层一起工作:

function createThunkMiddleware(extraArgument) {return ({ dispatch, getState }) => (next) => (action) => {if (typeof action === 'function') {return action(dispatch, getState, extraArgument);}
return next(action);};}
const thunk = createThunkMiddleware();thunk.withExtraArgument = createThunkMiddleware;
export default thunk;

https://github.com/reduxjs/redux-thunk

我认为至少有两个原因:

第一个原因是关注点的分离,调用api并取回数据不是动作创建者的工作,你必须向你的动作创建者函数传递两个参数,动作类型和有效负载。

第二个原因是因为redux存储正在等待具有强制操作类型和可选有效负载的普通对象(但在这里您也必须传递有效负载)。

动作创建者应该是一个普通对象,如下所示:

addTodo(text){//添加待办事项返回{类型:ADD_TODO,文本}}而Redux-Thunk中间件的工作是将API调用的结果分配给适当的操作。