支持/反对使用redux-saga与ES6发电机,与redux-thunk与ES2017异步/ wait

时间:2022-12-28 11:03:54

There is a lot of talk about the latest kid in redux town right now, redux-saga/redux-saga. It uses generator functions for listening to/dispatching actions.

很多人都在谈论redux镇最近的一个孩子,redux-saga/redux-saga。它使用生成器函数监听/调度操作。

Before I wrap my head around it, I would like to know the pros/cons of using redux-saga instead of the approach below where I'm using redux-thunk with async/await.

在结束之前,我想知道使用redux-saga而不是使用redux-thunk和async/ waiting的方法的优缺点。

A component might look like this, dispatch actions like usual.

组件可能是这样的,像往常一样分派操作。

import { login } from 'redux/auth';

class LoginForm extends Component {

  onClick(e) {
    e.preventDefault();
    const { user, pass } = this.refs;
    this.props.dispatch(login(user.value, pass.value));
  }

  render() {
    return (<div>
        <input type="text" ref="user" />
        <input type="password" ref="pass" />
        <button onClick={::this.onClick}>Sign In</button>
    </div>);
  } 
}

export default connect((state) => ({}))(LoginForm);

Then my actions look something like this:

然后我的行为看起来是这样的:

// auth.js

import request from 'axios';
import { loadUserData } from './user';

// define constants
// define initial state
// export default reducer

export const login = (user, pass) => async (dispatch) => {
    try {
        dispatch({ type: LOGIN_REQUEST });
        let { data } = await request.post('/login', { user, pass });
        await dispatch(loadUserData(data.uid));
        dispatch({ type: LOGIN_SUCCESS, data });
    } catch(error) {
        dispatch({ type: LOGIN_ERROR, error });
    }
}

// more actions...

// user.js

import request from 'axios';

// define constants
// define initial state
// export default reducer

export const loadUserData = (uid) => async (dispatch) => {
    try {
        dispatch({ type: USERDATA_REQUEST });
        let { data } = await request.get(`/users/${uid}`);
        dispatch({ type: USERDATA_SUCCESS, data });
    } catch(error) {
        dispatch({ type: USERDATA_ERROR, error });
    }
}

// more actions...

7 个解决方案

#1


366  

In redux-saga, the equivalent of the above example would be

在redux-saga中,相当于上面的例子

export function* loginSaga() {
  while(true) {
    const { user, pass } = yield take(LOGIN_REQUEST)
    try {
      let { data } = yield call(request.post, '/login', { user, pass });
      yield fork(loadUserData, data.uid);
      yield put({ type: LOGIN_SUCCESS, data });
    } catch(error) {
      yield put({ type: LOGIN_ERROR, error });
    }  
  }
}

export function* loadUserData(uid) {
  try {
    yield put({ type: USERDATA_REQUEST });
    let { data } = yield call(request.get, `/users/${uid}`);
    yield put({ type: USERDATA_SUCCESS, data });
  } catch(error) {
    yield put({ type: USERDATA_ERROR, error });
  }
}

The first thing to notice is that we're calling the api functions using the form yield call(func, ...args). call doesn't execute the effect, it just creates a plain object like {type: 'CALL', func, args}. The execution is delegated to the redux-saga middleware which takes care of executing the function and resuming the generator with its result.

首先要注意的是,我们正在使用form yield调用(func,…args)调用api函数。调用并不执行效果,它只创建一个普通的对象,比如{type: ' call ', func, args}。执行被委托给redux-saga中间件,它负责执行函数并恢复生成器的结果。

The main advantage is that you can test the generator outside of Redux using simple equality checks

主要的优点是您可以使用简单的等式检查来测试Redux之外的生成器

const iterator = loginSaga()

assert.deepEqual(iterator.next().value, take(LOGIN_REQUEST))

// resume the generator with some dummy action
const mockAction = {user: '...', pass: '...'}
assert.deepEqual(
  iterator.next(mockAction).value, 
  call(request.post, '/login', mockAction)
)

// simulate an error result
const mockError = 'invalid user/password'
assert.deepEqual(
  iterator.throw(mockError).value, 
  put({ type: LOGIN_ERROR, error: mockError })
)

Note we're mocking the api call result by simply injecting the mocked data into the next method of the iterator. Mocking data is way simpler than mocking functions.

注意,我们只是将模拟数据注入迭代器的下一个方法中,从而模拟了api调用结果。mock数据要比mock函数简单得多。

The second thing to notice is the call to yield take(ACTION). Thunks are called by the action creator on each new action (e.g. LOGIN_REQUEST). i.e. actions are continually pushed to thunks, and thunks have no control on when to stop handling those actions.

需要注意的第二件事是要求屈服(行动)。每个新动作(例如LOGIN_REQUEST)的动作创建者都调用Thunks。例如,动作被不断地推到拍子上,拍子无法控制何时停止处理这些动作。

In redux-saga, generators pull the next action. i.e. they have control when to listen for some action, and when to not. In the above example the flow instructions are placed inside a while(true) loop, so it'll listen for each incoming action, which somewhat mimics the thunk pushing behavior.

在redux-saga中,发电机拉动下一个动作。也就是说,他们可以控制什么时候听一些动作,什么时候不听。在上面的示例中,流指令被放置在一个while(true)循环中,因此它将侦听每个传入的操作,这在某种程度上模仿了thunk推动行为。

The pull approach allows implementing complex control flows. Suppose for example we want to add the following requirements

拉方法允许实现复杂的控制流。例如,假设我们要添加以下需求

  • Handle LOGOUT user action

    处理注销用户操作

  • upon the first successful login, the server returns a token which expires in some delay stored in a expires_in field. We'll have to refresh the authorization in the background on each expires_in milliseconds

    在第一次成功登录后,服务器将返回一个令牌,该令牌将过期,并将其存储在expres_in字段中。我们必须在每一个expires_毫秒的后台刷新授权

  • Take into account that when waiting for the result of api calls (either initial login or refresh) the user may logout in-between.

    考虑到在等待api调用(初始登录或刷新)结果时,用户可能会在两者之间登录。

How would you implement that with thunks; while also providing full test coverage for the entire flow? Here is how it may look with Sagas:

如何用thunks实现它;同时为整个流程提供完整的测试覆盖?下面是它与Sagas的关系:

function* authorize(credentials) {
  const token = yield call(api.authorize, credentials)
  yield put( login.success(token) )
  return token
}

function* authAndRefreshTokenOnExpiry(name, password) {
  let token = yield call(authorize, {name, password})
  while(true) {
    yield call(delay, token.expires_in)
    token = yield call(authorize, {token})
  }
}

function* watchAuth() {
  while(true) {
    try {
      const {name, password} = yield take(LOGIN_REQUEST)

      yield race([
        take(LOGOUT),
        call(authAndRefreshTokenOnExpiry, name, password)
      ])

      // user logged out, next while iteration will wait for the
      // next LOGIN_REQUEST action

    } catch(error) {
      yield put( login.error(error) )
    }
  }
}

In the above example, we're expressing our concurrency requirement using race. If take(LOGOUT) wins the race (i.e. user clicked on a Logout Button). The race will automatically cancel the authAndRefreshTokenOnExpiry background task. And if the authAndRefreshTokenOnExpiry was blocked in middle of a call(authorize, {token}) call it'll also be cancelled. Cancellation propagates downward automatically.

在上面的示例中,我们使用race来表示并发需求。如果take(LOGOUT)赢得比赛(即用户点击了LOGOUT按钮)。race将自动取消authandrefreshtokenon过期后台任务。如果authandrefreshtokenonexpiration被阻塞在调用中间(authorize, {token}),那么它也将被取消。取消自动向下传播。

You can find a runnable demo of the above flow

您可以找到上述流程的可运行演示

#2


69  

I will add my experience using saga in production system in addition to the library author's rather thorough answer.

我将把我在制作系统中使用saga的经验,加上图书馆作者相当详尽的回答。

Pro (using saga):

箴(使用传奇)。

  • Testability. It's very easy to test sagas as call() returns a pure object. Testing thunks normally requires you to include a mockStore inside your test.

    可测试性。测试sagas call()返回一个纯对象非常容易。测试thunks通常要求您在测试中包含一个mockStore。

  • redux-saga comes with lots of useful helper functions about tasks. It seems to me that the concept of saga is to create some kind of background worker/thread for your app, which act as a missing piece in react redux architecture(actionCreators and reducers must be pure functions.) Which leads to next point.

    redux-saga提供了许多关于任务的有用的助手函数。在我看来,saga的概念是为你的应用创建一些后台worker/thread,它们在react redux架构中扮演着一个缺失的角色(actioncreator和reducers必须是纯函数)。这就引出了下一点。

  • Sagas offer independent place to handle all side effects. It is usually easier to modify and manage than thunk actions in my experience.

    萨加斯提供了一个独立的地方来处理所有的副作用。在我的经验中,修改和管理行动通常比不行动更容易。

Con:

反对:

  • Generator syntax.

    发电机的语法。

  • Lots of concepts to learn.

    有很多概念需要学习。

  • API stability. It seems redux-saga is still adding features (eg Channels?) and the community is not as big. There is a concern if the library makes a non backward compatible update some day.

    API的稳定性。似乎redux-saga仍在增加功能(如频道?),而社区没有那么大。如果库某天进行了不向后兼容的更新,就会产生问题。

#3


15  

I'd just like to add some comments from my personal experience (using both sagas and thunk):

我想补充一些我个人经历的评论(同时使用sagas和thunk):

Sagas are great to test:

萨加斯非常适合测试:

  • You don't need to mock functions wrapped with effects
  • 您不需要使用带有效果的模拟函数
  • Therefore tests are clean, readable and easy to write
  • 因此,测试是干净的、可读的、易于编写的
  • When using sagas, action creators mostly return plain object literals. It is also easier to test and assert unlike thunk's promises.
  • 在使用sagas时,动作创建者主要返回的是普通的对象文字。与thunk的承诺不同,测试和断言也更容易。

Sagas are more powerful. All what you can do in one thunk's action creator you can also do in one saga, but not vice versa (or at least not easily). For example:

传奇更强大。在《坦克》的动作创作者身上,你所能做的一切,也可以在《英雄传奇》中,但反之亦然(至少不容易)。例如:

  • wait for an action/actions to be dispatched (take)
  • 等待一个动作/动作被分派(采取)
  • cancel existing routine (cancel, takeLatest, race)
  • 取消现有的常规(取消、取消、比赛)
  • multiple routines can listen to the same action (take, takeEvery, ...)
  • 多个例程可以听相同的动作(take, take, takeEvery,…)

Sagas also offers other useful functionality, which generalize some common application patterns:

Sagas还提供了其他有用的功能,这些功能概括了一些常见的应用程序模式:

  • channels to listen on external event sources (e.g. websockets)
  • 监听外部事件源的通道(例如websockets)
  • fork model (fork, spawn)
  • 叉模型(叉,产卵)
  • throttle
  • 节气门
  • ...

Sagas are great and powerful tool. However with the power comes responsibility. When your application grows you can get easily lost by figuring out who is waiting for the action to be dispatched, or what everything happens when some action is being dispatched. On the other hand thunk is simpler and easier to reason about. Choosing one or another depends on many aspects like type and size of the project, what types of side effect your project must handle or dev team preference. In any case just keep your application simple and predictable.

萨加斯是伟大而有力的工具。然而,伴随权力而来的是责任。当您的应用程序增长时,您很容易迷失方向,因为您要弄清楚谁在等待操作被分派,或者当一些操作被分派时发生了什么。另一方面,它更简单,更容易推理。选择一个或另一个取决于许多方面,如项目的类型和大小、项目必须处理的副作用类型或开发团队偏好。无论如何,只要保持应用程序简单和可预测即可。

#4


0  

Having reviewed a few different large scale React/Redux projects in my experience Sagas provide developers a more structured way of writing code that is much easier to test and harder to get wrong.

在我的经验中回顾了一些不同的大规模的反应/Redux项目,这为开发人员提供了一种更结构化的编写代码的方式,这种方式更容易测试,也更难出错。

Yes it is a little wierd to start with, but most devs get enough of an understanding of it in a day. I always tell people to not worry about what yield does to start with and that once you write a couple of test it will come to you.

是的,这是一个很小的开始,但是大多数的devs在一天之内得到了足够的理解。我总是告诉人们不要担心开始时的收益率,一旦你写了几个测试,它就会来找你。

I have seen a couple of projects where thunks have been treated as if they are controllers from the MVC patten and this quickly becomes an unmaintable mess.

我曾见过几个项目,在这些项目中,thunks被视为MVC patten的控制器,而这很快就变成了一场不可调和的混乱。

My advice is to use Sagas where you need A triggers B type stuff relating to a single event. For anything that could cut across a number of actions, I find it is simpler to write customer middleware and use the meta property of an FSA action to trigger it.

我的建议是使用Sagas,其中需要一个触发器B类型的东西与一个事件相关。对于任何可以跨越多个操作的东西,我发现编写客户中间件并使用FSA动作的元属性来触发它更简单。

#5


-1  

Here's a project that combines the best parts (pros) of both redux-saga and redux-thunk: you can handle all side-effects on sagas while getting a promise by dispatching the corresponding action: https://github.com/diegohaz/redux-saga-thunk

这是一个结合了redux-saga和redux-thunk最好的部分(优点)的项目:你可以处理sagas上的所有副作用,同时得到相应动作的承诺:https://github.com/diegohaz/redux-saga-thunk

class MyComponent extends React.Component {
  componentWillMount() {
    // `doSomething` dispatches an action which is handled by some saga
    this.props.doSomething().then((detail) => {
      console.log('Yaay!', detail)
    }).catch((error) => {
      console.log('Oops!', error)
    })
  }
}

#6


-1  

An easier way is to use redux-auto.

更简单的方法是使用redux-auto。

from the documantasion

从documantasion

redux-auto fixed this asynchronous problem simply by allowing you to create an "action" function that returns a promise. To accompany your "default" function action logic.

redux-auto仅仅通过允许您创建返回一个承诺的“动作”函数来修复这个异步问题。伴随您的“默认”功能动作逻辑。

  1. No need for other Redux async middleware. e.g. thunk, promise-middleware, saga
  2. 不需要其他Redux异步中间件。例如铛,promise-middleware,传奇
  3. Easily allows you to pass a promise into redux and have it managed for you
  4. 很容易让你将一个承诺传递给redux,并让它为你管理
  5. Allows you to co-locate external service calls with where they will be transformed
  6. 允许您将外部服务调用与它们将被转换的位置共同定位
  7. Naming the file "init.js" will call it once at app start. This is good for loading data from the server at start
  8. 命名该文件“init。js会在app开始时调用一次。这有利于在开始时从服务器加载数据

The idea is to have each action in a specific file. co-locating the server call in the file with reducer functions for "pending", "fulfilled" and "rejected". This makes handling promises very easy.

其思想是将每个操作都放在一个特定的文件中。在文件中使用“挂起”、“完成”和“拒绝”的还原剂函数共同定位服务器调用。这使得处理承诺非常容易。

It also automatically attaches a helper object(called "async") to the prototype of your state, allowing you to track in your UI, requested transitions.

它还自动将一个helper对象(称为“async”)附加到您的状态的原型,允许您在UI中跟踪请求的转换。

#7


-2  

One quick note. Generators are cancellable, async/await — not. So for an example from the question, it does not really make sense of what to pick. But for more complicated flows sometimes there is no better solution than using generators.

一个快速的注意。发电机是可取消,异步/等待-不是。举个问题中的例子,它其实没有什么意义。但对于更复杂的流,有时没有比使用生成器更好的解决方案。

So, another idea could be is to use generators with redux-thunk, but for me, it seems like trying to invent a bicycle with square wheels.

所以,另一个想法可能是使用带有redux-thunk的发电机,但对我来说,这似乎是在尝试发明一种有方形*的自行车。

And of course, generators are easier to test.

当然,发电机更容易测试。

#1


366  

In redux-saga, the equivalent of the above example would be

在redux-saga中,相当于上面的例子

export function* loginSaga() {
  while(true) {
    const { user, pass } = yield take(LOGIN_REQUEST)
    try {
      let { data } = yield call(request.post, '/login', { user, pass });
      yield fork(loadUserData, data.uid);
      yield put({ type: LOGIN_SUCCESS, data });
    } catch(error) {
      yield put({ type: LOGIN_ERROR, error });
    }  
  }
}

export function* loadUserData(uid) {
  try {
    yield put({ type: USERDATA_REQUEST });
    let { data } = yield call(request.get, `/users/${uid}`);
    yield put({ type: USERDATA_SUCCESS, data });
  } catch(error) {
    yield put({ type: USERDATA_ERROR, error });
  }
}

The first thing to notice is that we're calling the api functions using the form yield call(func, ...args). call doesn't execute the effect, it just creates a plain object like {type: 'CALL', func, args}. The execution is delegated to the redux-saga middleware which takes care of executing the function and resuming the generator with its result.

首先要注意的是,我们正在使用form yield调用(func,…args)调用api函数。调用并不执行效果,它只创建一个普通的对象,比如{type: ' call ', func, args}。执行被委托给redux-saga中间件,它负责执行函数并恢复生成器的结果。

The main advantage is that you can test the generator outside of Redux using simple equality checks

主要的优点是您可以使用简单的等式检查来测试Redux之外的生成器

const iterator = loginSaga()

assert.deepEqual(iterator.next().value, take(LOGIN_REQUEST))

// resume the generator with some dummy action
const mockAction = {user: '...', pass: '...'}
assert.deepEqual(
  iterator.next(mockAction).value, 
  call(request.post, '/login', mockAction)
)

// simulate an error result
const mockError = 'invalid user/password'
assert.deepEqual(
  iterator.throw(mockError).value, 
  put({ type: LOGIN_ERROR, error: mockError })
)

Note we're mocking the api call result by simply injecting the mocked data into the next method of the iterator. Mocking data is way simpler than mocking functions.

注意,我们只是将模拟数据注入迭代器的下一个方法中,从而模拟了api调用结果。mock数据要比mock函数简单得多。

The second thing to notice is the call to yield take(ACTION). Thunks are called by the action creator on each new action (e.g. LOGIN_REQUEST). i.e. actions are continually pushed to thunks, and thunks have no control on when to stop handling those actions.

需要注意的第二件事是要求屈服(行动)。每个新动作(例如LOGIN_REQUEST)的动作创建者都调用Thunks。例如,动作被不断地推到拍子上,拍子无法控制何时停止处理这些动作。

In redux-saga, generators pull the next action. i.e. they have control when to listen for some action, and when to not. In the above example the flow instructions are placed inside a while(true) loop, so it'll listen for each incoming action, which somewhat mimics the thunk pushing behavior.

在redux-saga中,发电机拉动下一个动作。也就是说,他们可以控制什么时候听一些动作,什么时候不听。在上面的示例中,流指令被放置在一个while(true)循环中,因此它将侦听每个传入的操作,这在某种程度上模仿了thunk推动行为。

The pull approach allows implementing complex control flows. Suppose for example we want to add the following requirements

拉方法允许实现复杂的控制流。例如,假设我们要添加以下需求

  • Handle LOGOUT user action

    处理注销用户操作

  • upon the first successful login, the server returns a token which expires in some delay stored in a expires_in field. We'll have to refresh the authorization in the background on each expires_in milliseconds

    在第一次成功登录后,服务器将返回一个令牌,该令牌将过期,并将其存储在expres_in字段中。我们必须在每一个expires_毫秒的后台刷新授权

  • Take into account that when waiting for the result of api calls (either initial login or refresh) the user may logout in-between.

    考虑到在等待api调用(初始登录或刷新)结果时,用户可能会在两者之间登录。

How would you implement that with thunks; while also providing full test coverage for the entire flow? Here is how it may look with Sagas:

如何用thunks实现它;同时为整个流程提供完整的测试覆盖?下面是它与Sagas的关系:

function* authorize(credentials) {
  const token = yield call(api.authorize, credentials)
  yield put( login.success(token) )
  return token
}

function* authAndRefreshTokenOnExpiry(name, password) {
  let token = yield call(authorize, {name, password})
  while(true) {
    yield call(delay, token.expires_in)
    token = yield call(authorize, {token})
  }
}

function* watchAuth() {
  while(true) {
    try {
      const {name, password} = yield take(LOGIN_REQUEST)

      yield race([
        take(LOGOUT),
        call(authAndRefreshTokenOnExpiry, name, password)
      ])

      // user logged out, next while iteration will wait for the
      // next LOGIN_REQUEST action

    } catch(error) {
      yield put( login.error(error) )
    }
  }
}

In the above example, we're expressing our concurrency requirement using race. If take(LOGOUT) wins the race (i.e. user clicked on a Logout Button). The race will automatically cancel the authAndRefreshTokenOnExpiry background task. And if the authAndRefreshTokenOnExpiry was blocked in middle of a call(authorize, {token}) call it'll also be cancelled. Cancellation propagates downward automatically.

在上面的示例中,我们使用race来表示并发需求。如果take(LOGOUT)赢得比赛(即用户点击了LOGOUT按钮)。race将自动取消authandrefreshtokenon过期后台任务。如果authandrefreshtokenonexpiration被阻塞在调用中间(authorize, {token}),那么它也将被取消。取消自动向下传播。

You can find a runnable demo of the above flow

您可以找到上述流程的可运行演示

#2


69  

I will add my experience using saga in production system in addition to the library author's rather thorough answer.

我将把我在制作系统中使用saga的经验,加上图书馆作者相当详尽的回答。

Pro (using saga):

箴(使用传奇)。

  • Testability. It's very easy to test sagas as call() returns a pure object. Testing thunks normally requires you to include a mockStore inside your test.

    可测试性。测试sagas call()返回一个纯对象非常容易。测试thunks通常要求您在测试中包含一个mockStore。

  • redux-saga comes with lots of useful helper functions about tasks. It seems to me that the concept of saga is to create some kind of background worker/thread for your app, which act as a missing piece in react redux architecture(actionCreators and reducers must be pure functions.) Which leads to next point.

    redux-saga提供了许多关于任务的有用的助手函数。在我看来,saga的概念是为你的应用创建一些后台worker/thread,它们在react redux架构中扮演着一个缺失的角色(actioncreator和reducers必须是纯函数)。这就引出了下一点。

  • Sagas offer independent place to handle all side effects. It is usually easier to modify and manage than thunk actions in my experience.

    萨加斯提供了一个独立的地方来处理所有的副作用。在我的经验中,修改和管理行动通常比不行动更容易。

Con:

反对:

  • Generator syntax.

    发电机的语法。

  • Lots of concepts to learn.

    有很多概念需要学习。

  • API stability. It seems redux-saga is still adding features (eg Channels?) and the community is not as big. There is a concern if the library makes a non backward compatible update some day.

    API的稳定性。似乎redux-saga仍在增加功能(如频道?),而社区没有那么大。如果库某天进行了不向后兼容的更新,就会产生问题。

#3


15  

I'd just like to add some comments from my personal experience (using both sagas and thunk):

我想补充一些我个人经历的评论(同时使用sagas和thunk):

Sagas are great to test:

萨加斯非常适合测试:

  • You don't need to mock functions wrapped with effects
  • 您不需要使用带有效果的模拟函数
  • Therefore tests are clean, readable and easy to write
  • 因此,测试是干净的、可读的、易于编写的
  • When using sagas, action creators mostly return plain object literals. It is also easier to test and assert unlike thunk's promises.
  • 在使用sagas时,动作创建者主要返回的是普通的对象文字。与thunk的承诺不同,测试和断言也更容易。

Sagas are more powerful. All what you can do in one thunk's action creator you can also do in one saga, but not vice versa (or at least not easily). For example:

传奇更强大。在《坦克》的动作创作者身上,你所能做的一切,也可以在《英雄传奇》中,但反之亦然(至少不容易)。例如:

  • wait for an action/actions to be dispatched (take)
  • 等待一个动作/动作被分派(采取)
  • cancel existing routine (cancel, takeLatest, race)
  • 取消现有的常规(取消、取消、比赛)
  • multiple routines can listen to the same action (take, takeEvery, ...)
  • 多个例程可以听相同的动作(take, take, takeEvery,…)

Sagas also offers other useful functionality, which generalize some common application patterns:

Sagas还提供了其他有用的功能,这些功能概括了一些常见的应用程序模式:

  • channels to listen on external event sources (e.g. websockets)
  • 监听外部事件源的通道(例如websockets)
  • fork model (fork, spawn)
  • 叉模型(叉,产卵)
  • throttle
  • 节气门
  • ...

Sagas are great and powerful tool. However with the power comes responsibility. When your application grows you can get easily lost by figuring out who is waiting for the action to be dispatched, or what everything happens when some action is being dispatched. On the other hand thunk is simpler and easier to reason about. Choosing one or another depends on many aspects like type and size of the project, what types of side effect your project must handle or dev team preference. In any case just keep your application simple and predictable.

萨加斯是伟大而有力的工具。然而,伴随权力而来的是责任。当您的应用程序增长时,您很容易迷失方向,因为您要弄清楚谁在等待操作被分派,或者当一些操作被分派时发生了什么。另一方面,它更简单,更容易推理。选择一个或另一个取决于许多方面,如项目的类型和大小、项目必须处理的副作用类型或开发团队偏好。无论如何,只要保持应用程序简单和可预测即可。

#4


0  

Having reviewed a few different large scale React/Redux projects in my experience Sagas provide developers a more structured way of writing code that is much easier to test and harder to get wrong.

在我的经验中回顾了一些不同的大规模的反应/Redux项目,这为开发人员提供了一种更结构化的编写代码的方式,这种方式更容易测试,也更难出错。

Yes it is a little wierd to start with, but most devs get enough of an understanding of it in a day. I always tell people to not worry about what yield does to start with and that once you write a couple of test it will come to you.

是的,这是一个很小的开始,但是大多数的devs在一天之内得到了足够的理解。我总是告诉人们不要担心开始时的收益率,一旦你写了几个测试,它就会来找你。

I have seen a couple of projects where thunks have been treated as if they are controllers from the MVC patten and this quickly becomes an unmaintable mess.

我曾见过几个项目,在这些项目中,thunks被视为MVC patten的控制器,而这很快就变成了一场不可调和的混乱。

My advice is to use Sagas where you need A triggers B type stuff relating to a single event. For anything that could cut across a number of actions, I find it is simpler to write customer middleware and use the meta property of an FSA action to trigger it.

我的建议是使用Sagas,其中需要一个触发器B类型的东西与一个事件相关。对于任何可以跨越多个操作的东西,我发现编写客户中间件并使用FSA动作的元属性来触发它更简单。

#5


-1  

Here's a project that combines the best parts (pros) of both redux-saga and redux-thunk: you can handle all side-effects on sagas while getting a promise by dispatching the corresponding action: https://github.com/diegohaz/redux-saga-thunk

这是一个结合了redux-saga和redux-thunk最好的部分(优点)的项目:你可以处理sagas上的所有副作用,同时得到相应动作的承诺:https://github.com/diegohaz/redux-saga-thunk

class MyComponent extends React.Component {
  componentWillMount() {
    // `doSomething` dispatches an action which is handled by some saga
    this.props.doSomething().then((detail) => {
      console.log('Yaay!', detail)
    }).catch((error) => {
      console.log('Oops!', error)
    })
  }
}

#6


-1  

An easier way is to use redux-auto.

更简单的方法是使用redux-auto。

from the documantasion

从documantasion

redux-auto fixed this asynchronous problem simply by allowing you to create an "action" function that returns a promise. To accompany your "default" function action logic.

redux-auto仅仅通过允许您创建返回一个承诺的“动作”函数来修复这个异步问题。伴随您的“默认”功能动作逻辑。

  1. No need for other Redux async middleware. e.g. thunk, promise-middleware, saga
  2. 不需要其他Redux异步中间件。例如铛,promise-middleware,传奇
  3. Easily allows you to pass a promise into redux and have it managed for you
  4. 很容易让你将一个承诺传递给redux,并让它为你管理
  5. Allows you to co-locate external service calls with where they will be transformed
  6. 允许您将外部服务调用与它们将被转换的位置共同定位
  7. Naming the file "init.js" will call it once at app start. This is good for loading data from the server at start
  8. 命名该文件“init。js会在app开始时调用一次。这有利于在开始时从服务器加载数据

The idea is to have each action in a specific file. co-locating the server call in the file with reducer functions for "pending", "fulfilled" and "rejected". This makes handling promises very easy.

其思想是将每个操作都放在一个特定的文件中。在文件中使用“挂起”、“完成”和“拒绝”的还原剂函数共同定位服务器调用。这使得处理承诺非常容易。

It also automatically attaches a helper object(called "async") to the prototype of your state, allowing you to track in your UI, requested transitions.

它还自动将一个helper对象(称为“async”)附加到您的状态的原型,允许您在UI中跟踪请求的转换。

#7


-2  

One quick note. Generators are cancellable, async/await — not. So for an example from the question, it does not really make sense of what to pick. But for more complicated flows sometimes there is no better solution than using generators.

一个快速的注意。发电机是可取消,异步/等待-不是。举个问题中的例子,它其实没有什么意义。但对于更复杂的流,有时没有比使用生成器更好的解决方案。

So, another idea could be is to use generators with redux-thunk, but for me, it seems like trying to invent a bicycle with square wheels.

所以,另一个想法可能是使用带有redux-thunk的发电机,但对我来说,这似乎是在尝试发明一种有方形*的自行车。

And of course, generators are easier to test.

当然,发电机更容易测试。