与ES6发电机vs还原的传奇

现在有很多人在谈论最新的孩子,REDX-SAGA / REDX-SAGA。 它使用生成器函数来监听/分派操作。

在我将头围绕在它上面之前,我想知道使用redux-saga优缺点,而不是下面的方法,我使用redux-thunk是异步/等待的redux-thunk

一个组件可能看起来像这样,像往常一样调度操作。

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

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

// 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...

在缩略语中,上面的例子相当于

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

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

主要优点是您可以使用简单的等式检查在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 })
)

请注意,我们只是将嘲讽的数据注入迭代器的next方法来模拟api调用结果。 嘲笑数据比嘲笑功能更简单。

第二件要注意的是调用yield take(ACTION) 。 动作创建者在每个新动作(例如LOGIN_REQUEST )上调用LOGIN_REQUEST 。 即行动不断被推到thunk,并thwks无法控制什么时候停止处理这些行动。

在还原传奇中,发电机组将采取下一步行动。 即他们可以控制何时听取某个动作,何时不听。 在上面的例子中,流程指令被放置在while(true)循环中,所以它会监听每个传入的动作,这有点模仿了thunk推送行为。

拉方法允许实施复杂的控制流程。 假设我们想要添加以下要求

  • 处理注销用户操作

  • 在第一次成功登录时,服务器会返回一个存储在expires_in字段中的某个延迟过期的令牌。 我们必须在每个expires_in毫秒后在后台刷新授权

  • 考虑到当等待api调用的结果(初始登录或刷新)时,用户可以在其间注销。

  • 你会怎样用thunk来实现它; 同时还为整个流程提供全面的测试覆盖率? 以下是萨加斯的外观:

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

    在上面的例子中,我们使用race来表达我们的并发需求。 如果take(LOGOUT)赢得比赛(即用户点击注销按钮)。 比赛将自动取消authAndRefreshTokenOnExpiry后台任务。 如果authAndRefreshTokenOnExpirycall(authorize, {token})调用中被阻止,它也会被取消。 取消自动向下传播。

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


    除了图书馆作者的全面回答之外,我还将在生产系统中加入我的经验。

    Pro(使用传奇):

  • 可测性。 随着call()返回一个纯粹的对象,测试传统非常容易。 测试thunk通常要求您在测试中包含一个mockStore。

  • redux-saga带有许多有用的帮助函数。 在我看来,传奇的概念是为你的应用创建某种背景工作者/线程,这是作为反应减少架构中的缺失部分(actionCreators和reducer必须是纯函数)。这导致下一点。

  • 萨加斯提供独立的地方来处理所有的副作用。 根据我的经验,修改和管理通常比thunk操作更容易。

  • 缺点:

  • 生成器语法。

  • 很多要学习的概念。

  • API稳定性。 看起来还有传奇还在增加功能(例如频道?),而社区并不大。 如果图书馆有一天会进行非向后兼容的更新,那么有一个问题。


  • 我只想从我的个人经历中添加一些评论(同时使用传说和thunk):

    萨加斯是伟大的测试:

  • 你不需要模拟包含效果的函数
  • 因此测试是干净的,可读的并且易于编写
  • 当使用传说时,动作创作者通常会返回普通的对象文字。 与thunk的承诺不同,测试和断言也更容易。
  • 萨加斯更强大。 所有你可以在一个thunk的动作创造者中做的事情,你也可以在一个传奇中做,但反之亦然(或者至少不容易)。 例如:

  • 等待一个行动/行动被派遣( take
  • 取消现有程序( canceltakeLatestrace
  • 多个例程可以听同一个动作( taketakeEvery ,...)
  • 萨加斯还提供了其他有用的功能,它概括了一些常见的应用程序模式:

  • 监听外部事件源的channels (例如websockets)
  • 叉子模型( forkspawn
  • 风门
  • ...
  • 萨加斯是伟大而强大的工具。 然而,权力是责任。 当您的应用程序增长时,您可以通过计算谁在等待要发送的操作,或者在发送某个操作时发生的所有事情,从而轻松地找到它。 另一方面,thunk更简单,更容易推理。 选择一个或另一个取决于诸如项目的类型和大小,项目必须处理什么类型的副作用或开发团队偏好等多个方面。 无论如何,只要保持你的应用程序简单和可预测。

    链接地址: http://www.djcxy.com/p/59049.html

    上一篇: saga with ES6 generators vs redux

    下一篇: How to dispatch a Redux action with a timeout?