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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/34570758/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 16:33:49  来源:igfitidea点击:

Why do we need middleware for async flow in Redux?

javascriptasynchronousreactjsreduxredux-thunk

提问by sbichenko

According to the docs, "Without middleware, Redux store only supports synchronous data flow". I don't understand why this is the case. Why can't the container component call the async API, and then dispatchthe actions?

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

For example, imagine a simple UI: a field and a button. When user pushes the button, the field gets populated with data from a remote server.

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

A field and a button

一个字段和一个按钮

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>;
    }
}

When the exported component is rendered, I can click the button and the input is updated correctly.

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

Note the updatefunction in the connectcall. It dispatches an action that tells the App that it is updating, and then performs an async call. After the call finishes, the provided value is dispatched as a payload of another action.

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

What is wrong with this approach? Why would I want to use Redux Thunk or Redux Promise, as the documentation suggests?

这种方法有什么问题?为什么我要使用 Redux Thunk 或 Redux Promise,正如文档所建议的那样?

EDIT:I searched the Redux repo for clues, and found that Action Creators were required to be pure functions in the past. For example, here's a user trying to provide a better explanation for async data flow:

编辑:我在 Redux repo 中搜索了线索,发现过去要求 Action Creators 是纯函数。例如,这里有一个用户试图为异步数据流提供更好的解释:

The action creator itself is still a pure function, but the thunk function it returns doesn't need to be, and it can do our async calls

action creator 本身仍然是一个纯函数,但是它返回的thunk 函数不需要是,它可以做我们的异步调用

Action creators are no longer required to be pure.So, thunk/promise middleware was definitely required in the past, but it seems that this is no longer the case?

动作创建者不再需要是纯粹的。所以,过去肯定需要 thunk/promise 中间件,但现在似乎不再如此?

回答by Dan Abramov

What is wrong with this approach? Why would I want to use Redux Thunk or Redux Promise, as the documentation suggests?

这种方法有什么问题?为什么我要使用 Redux Thunk 或 Redux Promise,正如文档所建议的那样?

There is nothing wrong with this approach. It's just inconvenient in a large application because you'll have different components performing the same actions, you might want to debounce some actions, or keep some local state like auto-incrementing IDs close to action creators, etc. So it is just easier from the maintenance point of view to extract action creators into separate functions.

这种方法没有任何问题。这在大型应用程序中很不方便,因为您将有不同的组件执行相同的操作,您可能希望对某些操作进行去抖动,或者保持一些本地状态,例如靠近操作创建者的自动递增 ID 等。所以它更容易将动作创建者提取到单独的函数中的维护观点。

You can read my answer to “How to dispatch a Redux action with a timeout”for a more detailed walkthrough.

您可以阅读我对“如何使用超时调度 Redux 操作”的回答,以获得更详细的演练。

Middleware like Redux Thunk or Redux Promise just gives you “syntax sugar” for dispatching thunks or promises, but you don't have touse it.

中间件像终极版咚或终极版无极只是给你“语法糖”派遣的thunk或承诺,但你不必须使用它。

So, without any middleware, your action creator might look like

所以,如果没有任何中间件,你的动作创建者可能看起来像

// action creator
function loadData(dispatch, userId) { // needs to dispatch, so it is first argument
  return fetch(`http://data.com/${userId}`)
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_DATA_FAILURE', err })
    );
}

// component
componentWillMount() {
  loadData(this.props.dispatch, this.props.userId); // don't forget to pass dispatch
}

But with Thunk Middleware you can write it like this:

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

// action creator
function 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 })
    );
}

// component
componentWillMount() {
  this.props.dispatch(loadData(this.props.userId)); // dispatch like you usually do
}

So there is no huge difference. One thing I like about the latter approach is that the component doesn't care that the action creator is async. It just calls dispatchnormally, it can also use mapDispatchToPropsto bind such action creator with a short syntax, etc. The components don't know how action creators are implemented, and you can switch between different async approaches (Redux Thunk, Redux Promise, Redux Saga) without changing the components. On the other hand, with the former, explicit approach, your components know exactlythat a specific call is async, and needs dispatchto be passed by some convention (for example, as a sync parameter).

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

Also think about how this code will change. Say we want to have a second data loading function, and to combine them in a single action creator.

还要考虑此代码将如何更改。假设我们想要第二个数据加载函数,并将它们组合在一个动作创建器中。

With the first approach we need to be mindful of what kind of action creator we are calling:

对于第一种方法,我们需要注意我们调用的动作创建者类型:

// action creators
function 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 async
    loadOtherData(dispatch, userId) // pass dispatch first: it's async
  );
}


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

With Redux Thunk action creators can dispatchthe result of other action creators and not even think whether those are synchronous or asynchronous:

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

// action creators
function 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!
  );
}


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

With this approach, if you later want your action creators to look into current Redux state, you can just use the second getStateargument passed to the thunks without modifying the calling code at all:

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

function loadSomeData(userId) {
  // Thanks to Redux Thunk I can use getState() here without changing callers
  return (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 })
      );
  }
}

If you need to change it to be synchronous, you can also do this without changing any calling code:

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

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

So the benefit of using middleware like Redux Thunk or Redux Promise is that components aren't aware of how action creators are implemented, and whether they care about Redux state, whether they are synchronous or asynchronous, and whether or not they call other action creators. The downside is a little bit of indirection, but we believe it's worth it in real applications.

所以使用像 Redux Thunk 或 Redux Promise 这样的中间件的好处是,组件不知道 action creators 是如何实现的,也不知道它们是否关心 Redux state,它们是同步的还是异步的,以及它们是否调用了其他 action creators . 缺点是有点间接,但我们相信在实际应用中它是值得的。

Finally, Redux Thunk and friends is just one possible approach to asynchronous requests in Redux apps. Another interesting approach is Redux Sagawhich lets you define long-running daemons (“sagas”) that take actions as they come, and transform or perform requests before outputting actions. This moves the logic from action creators into sagas. You might want to check it out, and later pick what suits you the most.

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

I searched the Redux repo for clues, and found that Action Creators were required to be pure functions in the past.

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

This is incorrect. The docs said this, but the docs were wrong.
Action creators were never required to be pure functions.
We fixed the docs to reflect that.

这是不正确的。文档是这么说的,但文档是错误的。
动作创建者从来不需要是纯函数。
我们修复了文档以反映这一点。

回答by Sebastien Lorber

You don't.

你没有。

But... you should use redux-saga :)

但是......你应该使用 redux-saga :)

Dan Abramov's answer is right about redux-thunkbut I will talk a bit more about redux-sagathat is quite similar but more powerful.

Dan Abramov 的回答是正确的,redux-thunk但我会更多地谈论redux-saga,它非常相似但更强大。

Imperative VS declarative

命令式 VS 声明式

  • DOM: jQuery is imperative / React is declarative
  • Monads: IO is imperative / Free is declarative
  • Redux effects: redux-thunkis imperative / redux-sagais declarative
  • DOM:jQuery 是命令式的 / React 是声明式的
  • Monads: IO 是命令式的 / Free 是声明式的
  • Redux 效果redux-thunk命令式/redux-saga声明式

When you have a thunk in your hands, like an IO monad or a promise, you can't easily know what it will do once you execute. The only way to test a thunk is to execute it, and mock the dispatcher (or the whole outside world if it interacts with more stuff...).

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

If you are using mocks, then you are not doing functional programming.

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

Seen through the lens of side-effects, mocks are a flag that your code is impure, and in the functional programmer's eye, proof that something is wrong. Instead of downloading a library to help us check the iceberg is intact, we should be sailing around it. A hardcore TDD/Java guy once asked me how you do mocking in Clojure. The answer is, we usually don't. We usually see it as a sign we need to refactor our code.

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

Source

来源

The sagas (as they got implemented in redux-saga) are declarative and like the Free monad or React components, they are much easier to test without any mock.

传奇(因为它们在 中实现redux-saga)是声明性的,就像 Free monad 或 React 组件一样,它们更容易测试而无需任何模拟。

See also this article:

另见这篇文章

in modern FP, we shouldn't write programs — we should write descriptions of programs, which we can then introspect, transform, and interpret at will.

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

(Actually, Redux-saga is like a hybrid: the flow is imperative but the effects are declarative)

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

Confusion: actions/events/commands...

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

There is a lot of confusion in the frontend world on how some backend concepts like CQRS / EventSourcing and Flux / Redux may be related, mostly because in Flux we use the term "action" which can sometimes represent both imperative code (LOAD_USER) and events (USER_LOADED). I believe that like event-sourcing, you should only dispatch events.

在前端世界中,关于 CQRS/EventSourcing 和 Flux/Redux 等一些后端概念如何相关,存在很多混淆,主要是因为在 Flux 中我们使用术语“动作”,它有时可以同时表示命令式代码 ( LOAD_USER) 和事件 ( USER_LOADED)。我相信像事件溯源一样,你应该只发送事件。

Using sagas in practice

在实践中使用 sagas

Imagine an app with a link to a user profile. The idiomatic way to handle this with each middleware would be:

想象一个带有用户个人资料链接的应用程序。使用每个中间件处理此问题的惯用方法是:

redux-thunk

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

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 })
  }
}

This saga translates to:

这个传奇翻译成:

every time a username gets clicked, fetch the user profile and then dispatch an event with the loaded profile.

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

As you can see, there are some advantages of redux-saga.

如您所见,redux-saga.

The usage of takeLatestpermits to express that you are only interested to get the data of the last username clicked (handle concurrency problems in case the user click very fast on a lot of usernames). This kind of stuff is hard with thunks. You could have used takeEveryif you don't want this behavior.

takeLatest许可证的用法表示你只对最后点击的用户名感兴趣(处理并发问题,以防用户在很多用户名上点击非常快)。这种东西很难用 thunk。takeEvery如果您不想要这种行为,您可以使用。

You keep action creators pure. Note it's still useful to keep actionCreators (in sagas putand components dispatch), as it might help you to add action validation (assertions/flow/typescript) in the future.

你让动作创造者保持纯净。请注意,保留 actionCreators(在 sagasput和 components 中dispatch)仍然很有用,因为它可能会帮助您在未来添加动作验证(断言/流程/打字稿)。

Your code becomes much more testable as the effects are declarative

由于效果是声明性的,您的代码变得更加可测试

You don't need anymore to trigger rpc-like calls like actions.loadUser(). Your UI just needs to dispatch what HAS HAPPENED. We only fire events(always in the past tense!) and not actions anymore. This means that you can create decoupled "ducks"or Bounded Contextsand that the saga can act as the coupling point between these modular components.

您不再需要触发类似 rpc 的调用,例如actions.loadUser(). 您的 UI 只需要发送已经发生的事情。我们只触发事件(总是过去时!),不再触发动作。这意味着您可以创建解耦的“鸭子”有界上下文,并且 saga 可以充当这些模块化组件之间的耦合点。

This means that your views are more easy to manage because they don't need anymore to contain that translation layer between what has happened and what should happen as an effect

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

For example imagine an infinite scroll view. CONTAINER_SCROLLEDcan lead to NEXT_PAGE_LOADED, but is it really the responsibility of the scrollable container to decide whether or not we should load another page? Then he has to be aware of more complicated stuff like whether or not the last page was loaded successfully or if there is already a page that tries to load, or if there is no more items left to load? I don't think so: for maximum reusability the scrollable container should just describe that it has been scrolled. The loading of a page is a "business effect" of that scroll

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

Some might argue that generators can inherently hide state outside of redux store with local variables, but if you start to orchestrate complex things inside thunks by starting timers etc you would have the same problem anyway. And there's a selecteffect that now permits to get some state from your Redux store.

有些人可能会争辩说,生成器本质上可以使用局部变量将状态隐藏在 redux 存储之外,但是如果您通过启动计时器等开始在 thunk 中编排复杂的事情,无论如何您都会遇到同样的问题。select现在有一种效果允许从您的 Redux 存储中获取一些状态。

Sagas can be time-traveled and also enables complex flow logging and dev-tools that are currently being worked on. Here is some simple async flow logging that is already implemented:

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

saga flow logging

传奇流记录

Decoupling

解耦

Sagas are not only replacing redux thunks. They come from backend / distributed systems / event-sourcing.

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

It is a very common misconception that sagas are just here to replace your redux thunks with better testability. Actually this is just an implementation detail of redux-saga. Using declarative effects is better than thunks for testability, but the saga pattern can be implemented on top of imperative or declarative code.

一个非常普遍的误解是,saga 只是为了用更好的可测试性替换你的 redux thunk。其实这只是 redux-saga 的一个实现细节。在可测试性方面,使用声明式效果比 thunk 更好,但 saga 模式可以在命令式或声明式代码之上实现。

In the first place, the saga is a piece of software that permits to coordinate long running transactions (eventual consistency), and transactions across different bounded contexts (domain driven design jargon).

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

To simplify this for frontend world, imagine there is widget1 and widget2. When some button on widget1 is clicked, then it should have an effect on widget2. Instead of coupling the 2 widgets together (ie widget1 dispatch an action that targets widget2), widget1 only dispatch that its button was clicked. Then the saga listen for this button click and then update widget2 by dispaching a new event that widget2 is aware of.

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

This adds a level of indirection that is unnecessary for simple apps, but make it more easy to scale complex applications. You can now publish widget1 and widget2 to different npm repositories so that they never have to know about each others, without having them to share a global registry of actions. The 2 widgets are now bounded contexts that can live separately. They do not need each others to be consistent and can be reused in other apps as well. The saga is the coupling point between the two widgets that coordinate them in a meaningful way for your business.

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

Some nice articles on how to structure your Redux app, on which you can use Redux-saga for decoupling reasons:

一些关于如何构建 Redux 应用程序的好文章,出于解耦的原因,您可以在这些文章上使用 Redux-saga:

A concrete usecase: notification system

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

I want my components to be able to trigger the display of in-app notifications. But I don't want my components to be highly coupled to the notification system that has its own business rules (max 3 notifications displayed at the same time, notification queueing, 4 seconds display-time etc...).

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

I don't want my JSX components to decide when a notification will show/hide. I just give it the ability to request a notification, and leave the complex rules inside the saga. This kind of stuff is quite hard to implement with thunks or promises.

我不希望我的 JSX 组件决定何时显示/隐藏通知。我只是赋予它请求通知的能力,并将复杂的规则留在传奇中。这种东西很难用 thunk 或 promise 来实现。

notifications

通知

I've described herehow this can be done with saga

我已经在这里描述如何用 saga 做到这一点

Why is it called a Saga?

为什么叫佐贺呢?

The term saga comes from the backend world. I initially introduced Yassine (the author of Redux-saga) to that term in a long discussion.

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

Initially, that term was introduced with a paper, the saga pattern was supposed to be used to handle eventual consistency in distributed transactions, but its usage has been extended to a broader definition by backend developers so that it now also covers the "process manager" pattern (somehow the original saga pattern is a specialized form of process manager).

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

Today, the term "saga" is confusing as it can describe 2 different things. As it is used in redux-saga, it does not describe a way to handle distributed transactions but rather a way to coordinate actions in your app. redux-sagacould also have been called redux-process-manager.

今天,“传奇”一词令人困惑,因为它可以描述两种不同的事物。由于它在 redux-saga 中使用,它没有描述一种处理分布式事务的方法,而是一种协调应用程序中操作的方法。redux-saga也可以叫redux-process-manager

See also:

也可以看看:

Alternatives

备择方案

If you don't like the idea of using generators but you are interested by the saga pattern and its decoupling properties, you can also achieve the same with redux-observablewhich uses the name epicto describe the exact same pattern, but with RxJS. If you're already familiar with Rx, you'll feel right at home.

如果您不喜欢使用生成器的想法,但您对 saga 模式及其解耦属性感兴趣,您也可以使用redux-observable实现相同的目的,它使用名称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
        }))
    );

Some redux-saga useful resources

一些 redux-saga 有用的资源

2017 advises

2017年建议

  • Don't overuse Redux-saga just for the sake of using it. Testable API calls only are not worth it.
  • Don't remove thunks from your project for most simple cases.
  • Don't hesitate to dispatch thunks in yield put(someActionThunk)if it makes sense.
  • 不要仅仅为了使用它而过度使用 Redux-saga。仅可测试的 API 调用是不值得的。
  • 对于大多数简单的情况,不要从项目中删除 thunk。
  • yield put(someActionThunk)如果有意义,请不要犹豫,立即发送 thunk 。

If you are frightened of using Redux-saga (or Redux-observable) but just need the decoupling pattern, check redux-dispatch-subscribe: it permits to listen to dispatches and trigger new dispatches in listener.

如果您害怕使用 Redux-saga(或 Redux-observable)但只需要解耦模式,请检查redux-dispatch-subscribe:它允许侦听调度并在侦听器中触发新调度。

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

回答by acjay

The short answer: seems like a totally reasonable approach to the asynchrony problem to me. With a couple caveats.

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

I had a very similar line of thought when working on a new project we just started at my job. I was a big fan of vanilla Redux's elegant system for updating the store and rerendering components in a way that stays out of the guts of a React component tree. It seemed weird to me to hook into that elegant dispatchmechanism to handle asynchrony.

在我们刚开始工作的一个新项目中,我有一个非常相似的想法。我是 vanilla Redux 优雅系统的忠实粉丝,该系统以一种不受 React 组件树结构影响的方式更新存储和重新渲染组件。在我看来,使用这种优雅的dispatch机制来处理异步似乎很奇怪。

I ended up going with a really similar approach to what you have there in a library I factored out of our project, which we called react-redux-controller.

我最终采用了一种与我从我们的项目中提取的库中所拥有的非常相似的方法,我们称之为react-redux-controller

I ended up not going with the exact approach you have above for a couple reasons:

由于以下几个原因,我最终没有采用您上面的确切方法:

  1. The way you have it written, those dispatching functions don't have access to the store. You can somewhat get around that by having your UI components pass in all of the info the dispatching function needs. But I'd argue that this couples those UI components to the dispatching logic unnecessarily. And more problematically, there's no obvious way for the dispatching function to access updated state in async continuations.
  2. The dispatching functions have access to dispatchitself via lexical scope. This limits the options for refactoring once that connectstatement gets out of hand -- and it's looking pretty unwieldy with just that one updatemethod. So you need some system for letting you compose those dispatcher functions if you break them up into separate modules.
  1. 按照您的编写方式,那些调度函数无权访问商店。您可以通过让 UI 组件传入调度函数所需的所有信息来解决这个问题。但我认为这不必要地将这些 UI 组件与调度逻辑耦合在一起。更成问题的是,调度函数没有明显的方法来访问异步延续中的更新状态。
  2. 调度函数可以dispatch通过词法作用域访问自身。一旦该connect语句失控,这就限制了重构的选项——而且仅使用这种update方法看起来非常笨拙。因此,如果您将它们分解为单独的模块,则您需要一些系统来让您组合这些调度程序功能。

Take together, you have to rig up some system to allow dispatchand the store to be injected into your dispatching functions, along with the parameters of the event. I know of three reasonable approaches to this dependency injection:

总之,您必须装配一些系统以允许dispatch将 store 以及事件的参数注入到您的调度函数中。我知道这种依赖注入的三种合理方法:

  • redux-thunkdoes this in a functional way, by passing them into your thunks (making them not exactly thunks at all, by dome definitions). I haven't worked with the other dispatchmiddleware approaches, but I assume they're basically the same.
  • react-redux-controller does this with a coroutine. As a bonus, it also gives you access to the "selectors", which are the functions you may have passed in as the first argument to connect, rather than having to work directly with the raw, normalized store.
  • You could also do it the object-oriented way by injecting them into the thiscontext, through a variety of possible mechanisms.
  • redux-thunk通过将它们传递到您的 thunk(根据圆顶定义,使它们根本不完全是 thunk)以功能性方式做到这一点。我没有使用过其他dispatch中间件方法,但我认为它们基本相同。
  • react-redux-controller 使用协程来做到这一点。作为奖励,它还使您可以访问“选择器”,这些函数是您可能作为第一个参数传入的函数connect,而不必直接使用原始的、规范化的存储。
  • 您还可以this通过各种可能的机制将它们注入上下文,从而以面向对象的方式实现。

Update

更新

It occurs to me that part of this conundrum is a limitation of react-redux. The first argument to connectgets a state snapshot, but not dispatch. The second argument gets dispatch but not the state. Neither argument gets a thunk that closes over the current state, for being able to see updated state at the time of a continuation/callback.

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

回答by XML

Abramov's goal - and everyone's ideally - is simply to encapsulate complexity (and async calls) in the place where it's most appropriate.

Abramov 的目标——理想情况下是每个人的目标——只是将复杂性(和异步调用)封装在最合适的地方

Where's the best place to do that in the standard Redux dataflow? How about:

在标准 Redux 数据流中执行此操作的最佳位置在哪里?怎么样:

  • Reducers? No way. They should be pure functions with no side-effects. Updating the store is serious, complicated business. Don't contaminate it.
  • Dumb View Components?Definitely No. They have one concern: presentation and user-interaction, and should be as simple as possible.
  • Container Components?Possible, but sub-optimal. It makes sense in that the container is a place where we encapsulate some view related complexity and interact with the store, but:
    • Containers do need to be more complex than dumb components, but it's still a single responsibility: providing bindings between view and state/store. Your async logic is a whole separate concern from that.
    • By placing it in a container, you'd be locking your async logic into a single context, for a single view/route. Bad idea. Ideally it's all reusable, and totally decoupled.
  • Some other Service Module?Bad idea: you'd need to inject access to the store, which is a maintainability/testability nightmare. Better to go with the grain of Redux and access the store only using the APIs/models provided.
  • Actions and the Middlewares that interpret them?Why not?! For starters, it's the only major option we have left. :-) More logically, the action system is decoupled execution logic that you can use from anywhere. It's got access to the store and can dispatch more actions. It has a single responsibility which is to organize the flow of control and data around the application, and most async fits right into that.
    • What about the Action Creators? Why not just do async in there, instead of in the actions themselves, and in Middleware?
      • First and most important, the creators don't have access to the store, as middleware does. That means you can't dispatch new contingent actions, can't read from the store to compose your async, etc.
      • So, keep complexity in a place that's complex of necessity, and keep everything else simple. The creators can then be simple, relatively pure functions that are easy to test.
  • 减速机?没门。它们应该是没有副作用的纯函数。更新商店是一项严肃而复杂的工作。不要污染它。
  • 哑视图组件?绝对不。他们关心一个问题:演示和用户交互,并且应该尽可能简单。
  • 容器组件?可能,但次优。这是有道理的,因为容器是我们封装一些与视图相关的复杂性并与存储交互的地方,但是:
    • 容器确实需要比哑组件更复杂,但它仍然是一个单一的职责:提供视图和状态/存储之间的绑定。您的异步逻辑与此完全不同。
    • 通过将其放置在容器中,您会将异步逻辑锁定到单个上下文中,用于单个视图/路由。馊主意。理想情况下,它都是可重用的,并且完全解耦。
  • š青梅等服务模块?坏主意:您需要注入对商店的访问权限,这是可维护性/可测试性的噩梦。更好地使用 Redux 并仅使用提供的 API/模型访问商店。
  • 操作和解释它们的中间件?为什么不?!首先,这是我们剩下的唯一主要选择。:-) 更合乎逻辑的是,动作系统是可以在任何地方使用的解耦执行逻辑。它可以访问 store 并且可以调度更多的 action。它有一个单一的职责,即围绕应用程序组织控制流和数据流,大多数异步都适合这一点。
    • 动作创作者呢?为什么不在那里做异步,而不是在动作本身和中间件中?
      • 首先,也是最重要的一点,创建者无法像中间件那样访问商店。这意味着您无法分派新的或有操作,无法从存储中读取以组成异步等。
      • 因此,将复杂性放在必要的复杂位置,并保持其他一切简单。然后,创建者可以是易于测试的简单、相对纯的函数。

回答by Michelle Tilley

To answer the question that is asked in the beginning:

回答一开始提出的问题:

Why can't the container component call the async API, and then dispatch the actions?

为什么容器组件不能调用async API,然后dispatch action?

Keep in mind that those docs are for Redux, not Redux plus React. Redux stores hooked up to React componentscan do exactly what you say, but a Plain Jane Redux store with no middleware doesn't accept arguments to dispatchexcept plain ol' objects.

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

Without middleware you could of course still do

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

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

But it's a similar case where the asynchrony is wrapped aroundRedux rather than handled byRedux. So, middleware allows for asynchrony by modifying what can be passed directly to dispatch.

但这是一个类似的情况,异步是围绕Redux 而不是Redux处理。因此,中间件通过修改可以直接传递给dispatch.



That said, the spirit of your suggestion is, I think, valid. There are certainly other ways you could handle asynchrony in a Redux + React application.

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

One benefit of using middleware is that you can continue to use action creators as normal without worrying about exactly how they're hooked up. For example, using redux-thunk, the code you wrote would look a lot like

使用中间件的一个好处是您可以继续像往常一样使用动作创建器,而不必担心它们究竟是如何连接的。例如,使用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);

which doesn't look all that different from the original — it's just shuffled a bit — and connectdoesn't know that updateThingis (or needs to be) asynchronous.

它看起来与原来的没什么不同——它只是稍微调整了一下——并且connect不知道它updateThing是(或需要是)异步的。

If you also wanted to support promises, observables, sagas, or crazy customand highly declarativeaction creators, then Redux can do it just by changing what you pass to dispatch(aka, what you return from action creators). No mucking with the React components (or connectcalls) necessary.

如果您还想支持promisesobservablessagas疯狂的自定义高度声明性的动作创建者,那么 Redux 可以通过更改您传递给的内容dispatch(也就是您从动作创建者返回的内容)来实现。无需处理 React 组件(或connect调用)。

回答by Alireza

OK,let's start to see how middleware working first, that quite answer the question, this is the source code applyMiddleWarefunction in Redux:

好的,让我们先来看看中间件是如何工作的,这很好地回答了这个问题,这是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
      });
    };
  };
}

Look at this part, see how our dispatchbecome a function.

看看这部分,看看我们的dispatch是如何变成函数的

  ...
  getState: store.getState,
  dispatch: function dispatch(action) {
  return _dispatch(action);
}
  • Note that each middleware will be given the dispatchand getStatefunctions as named arguments.
  • 请注意,每个中间件都将被赋予dispatchgetState函数作为命名参数。

OK, this is how Redux-thunkas one of the most used middlewares for Redux introduce itself:

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

Redux Thunk middleware allows you to write action creators that return a function instead of an action. The thunk can be used to delay the dispatch of an action, or to dispatch only if a certain condition is met. The inner function receives the store methods dispatch and getState as parameters.

Redux Thunk 中间件允许您编写返回函数而不是动作的动作创建器。thunk 可用于延迟动作的调度,或仅在满足特定条件时才调度。内部函数接收 store 方法 dispatch 和 getState 作为参数。

So as you see, it will return a function instead an action, means you can wait and call it anytime you want as it's a function...

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

So what the heck is thunk? That's how it's introduced in Wikipedia:

那么 thunk 到底是什么?这就是它在维基百科中的介绍:

In computer programming, a thunk is a subroutine used to inject an additional calculation into another subroutine. Thunks are primarily used to delay a calculation until it is needed, or to insert operations at the beginning or end of the other subroutine. They have a variety of other applications to compiler code generation and in modular programming.

The term originated as a jocular derivative of "think".

A thunk is a function that wraps an expression to delay its evaluation.

在计算机编程中,thunk 是用于将附加计算注入另一个子程序的子程序。Thunk 主要用于延迟计算直到需要它,或者在其他子程序的开头或结尾插入操作。它们在编译器代码生成和模块化编程方面有多种其他应用。

该术语起源于“思考”的诙谐派生词。

thunk 是一种包装表达式以延迟其计算的函数。

//calculation of 1 + 2 is immediate 
//x === 3 
let 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;

So see how easy the concept is and how it can help you manage your async actions...

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

That's something you can live without it, but remember in programming there are always better, neater and proper ways to do things...

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

Apply middleware Redux

应用中间件 Redux

回答by SM Chinna

To use Redux-saga is the best middleware in React-redux implementation.

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

Ex: store.js

例如: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;

And then saga.js

然后 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 json
       yield* 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()
       ]
    }

And then action.js

然后 action.js

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

And then reducer.js

然后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; 

And then main.js

然后 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'));

try this.. is working

试试这个..正在工作

回答by Daniel

There are synchronous action creators and then there are asynchronous action creators.

有同步动作创建者,然后有异步动作创建者。

A synchronous action creator is one that when we call it, it immediately returns an Action object with all the relevant data attached to that object and its ready to be processed by our reducers.

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

Asynchronous action creators is one in which it will require a little bit of time before it is ready to eventually dispatch an action.

异步动作创建器需要一点时间才能准备好最终分派动作。

By definition, anytime you have an action creator that makes a network request, it is always going to qualify as an async action creator.

根据定义,任何时候你有一个发出网络请求的动作创建者,它总是有资格作为异步动作创建者。

If you want to have asynchronous action creators inside of a Redux application you have to install something called a middleware that is going to allow you to deal with those asynchronous action creators.

如果您想在 Redux 应用程序中使用异步操作创建器,则必须安装称为中间件的东西,它允许您处理这些异步操作创建器。

You can verify this in the error message that tells us use custom middleware for async actions.

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

So what is a middleware and why do we need it for async flow in Redux?

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

In the context of redux middleware such as redux-thunk, a middleware helps us deal with asynchronous action creators as that is something that Redux cannot handle out of the box.

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

With a middleware integrated into the Redux cycle, we are still calling action creators, that is going to return an action that will be dispatched but now when we dispatch an action, rather than sending it directly off to all of our reducers, we are going to say that an action will be sent through all the different middleware inside the application.

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

Inside of a single Redux app, we can have as many or as few middleware as we want. For the most part, in the projects we work on we will have one or two middleware hooked up to our Redux store.

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

A middleware is a plain JavaScript function that will be called with every single action that we dispatch. Inside of that function a middleware has the opportunity to stop an action from being dispatched to any of the reducers, it can modify an action or just mess around with an action in any way you which for example, we could create a middleware that console logs every action you dispatch just for your viewing pleasure.

中间件是一个普通的 JavaScript 函数,它将在我们分派的每一个动作中被调用。在该函数内部,中间件有机会阻止将动作分派到任何减速器,它可以修改动作或以任何方式处理动作,例如,我们可以创建一个控制台记录的中间件您发送的每一个动作只是为了您的观看乐趣。

There are a tremendous number of open source middleware you can install as dependencies into your project.

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

You are not limited to only making use of open source middleware or installing them as dependencies. You can write your own custom middleware and use it inside of your Redux store.

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

One of the more popular uses of middleware (and getting to your answer) is for dealing with asynchronous action creators, probably the most popular middleware out there is redux-thunk and it is about helping you deal with asynchronous action creators.

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

There are many other types of middleware that also help you in dealing with asynchronous action creators.

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

回答by Mselmi Ali

To Answer the question:

回答问题:

Why can't the container component call the async API, and then dispatch the actions?

为什么容器组件不能调用async API,然后dispatch action?

I would say for at least two reasons:

我会说至少有两个原因:

The first reason is the separation of concerns, it's not the job of the action creatorto call the apiand get data back, you have to have to pass two argument to your action creator function, the action typeand a payload.

第一个原因是关注点分离,action creator调用 theapi并取回数据不是 the 的工作,您必须将两个参数传递给您的action creator function、 theaction type和 a payload

The second reason is because the redux storeis waiting for a plain object with mandatory action type and optionally a payload(but here you have to pass the payload too).

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

The action creator should be a plain object like below:

动作创建者应该是一个简单的对象,如下所示:

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

And the job of Redux-Thunk midlewareto dispachethe result of your api callto the appropriate action.

和的工作Redux-Thunk midleware,以dispache您的结果api call来适当action

回答by Feras

When working in an enterprise project, there are many requirements available in middle-ware such as (saga) not available in simple asynchronous flow, below are some:

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

  • Running request in parallel
  • Pulling future actions without the need to wait
  • Non-blocking calls Race effect, example pickup first
  • response to initiate the process Sequencing your tasks (first in first call)
  • Composing
  • Task cancellation Dynamically forking the task.
  • Support Concurrency Running Saga outside the redux middleware.
  • Using channels
  • 并行运行请求
  • 无需等待即可拉取未来的操作
  • 非阻塞调用 Race 效果,例如先接听
  • 启动流程的响应 对您的任务进行排序(第一个调用中的第一个)
  • 构成
  • 任务取消 动态分叉任务。
  • 在 redux 中间件之外支持并发运行 Saga。
  • 使用渠道

The list is long just review the advanced section in saga documentation

列表很长,只需查看saga 文档中的高级部分