美文网首页Redux思想Web前端之路React Native实践
在React/Redux应用中使用Sagas管理异步操作(翻译)

在React/Redux应用中使用Sagas管理异步操作(翻译)

作者: smartphp | 来源:发表于2017-02-27 19:24 被阅读2684次
在React/Redux应用中使用Sagas管理异步操作

参考这篇文章,译文可能先幼稚,参考看看吧.redux-saga本身的一些概念很难
可以也参考 redux-saga的文档

Redux是一个和Flux类似的框架,在React社区中增长很快.他通过使用单个状态的原子性和纯函数式reduce来更新state,从而加强单向数据流,减小了数据操作的复杂性.
对于我,配置React+Flux一直是一根肉中刺,包括有Action creators的协作,异步操作也是非常的棘手.解决办法是在React组件中使用生命周期方法(life cycle),例如componentDidupdate,componentWillUpdate等等,在action creators中通过返回thunks(类似Promise对象)对象也可以工作.但是这些方法似乎在有些条件下会不太好使用.
我了更好的表达我的意思,我们来看看一个简单的Timer App. 整个APP的代码可在这里.

计时器APP

这个app允许使用者开始和停止一个定时器,也可以重置它.

计时器-app

我们可以把这个app可以看做一个在stopped和Running两个状态之间相互转变的有限状态机(finite machine).参见下面的简图.当timer在Running状态时,状态机会每一秒种更新app一次.

状态机

让我首先把app的基本设置看一下,然后我们演示一下怎么在action creators和React组件之外使用sagas帮助管理异步操作(side-effects).

Actions

在模块中有四个actions

  1. START-计时器改变为运行状态.
  2. TICK-时钟每个滴答以后递增定时器
  3. STOP-计时器改变为停止状态
  4. RESET-复位定时器
  // actions.js,四种action

export default { start: () => ({ type: 'START' })
               , tick: () => ({ type: 'TICK' })
               , stop: () => ({ type: 'STOP' })
               , reset: () => ({ type: 'RESET' })
               };

状态模型和Reducer

计时器的状态由两部分属性组成:status和seconds

 type Model = {
  status: string;
  seconds: number;
 }

status是运行和停止两个状态,seconds只要定时器开始计时就开始累积.

Reducer的实际代码如下

  // reducer.js

const INITIAL_STATE = { status: 'Stopped'
                      , seconds: 0
                      };

export default (state = INITIAL_STATE, action = null) => {
  switch (action.type) {
    case 'START':
      return { ...state, status: 'Running' };
    case 'STOP':
      return { ...state, status: 'Stopped' };
    case 'TICK':
      return { ...state, seconds: state.seconds + 1 };
    case 'RESET':
      return { ...state, seconds: 0 };
    default:
      return state;
  }
};

Timer的UI视图

视图(view)是比较单纯的的,所以和异步操作是完全隔绝的(side-effects free).视图渲染当前的时间和状态.于此同时在用户点击Reset,Start或Stop按钮的时候唤醒相应的回调函数.

export const Timer = ({ start, stop, reset, state }) => (
  <div>
    <p>
      { getFormattedTime(state) } ({ state.status })
    </p>
    <button
      disabled={state.status === 'Running'}
      onClick={() => reset()}>
      Reset
    </button>
    <button
      disabled={state.status === 'Running'}
      onClick={() => start()}>
      Start
    </button>
    <button
      disabled={state.status === 'Stopped'}
      onClick={stop}>
      Stop
    </button>
  </div>
);

问题:怎么处理周期性的更新操作.

目前app的状态是在运行和停止之间转变,但是还没有周期性改变定时器的机制.
在典型的Redux+React的app中,有两种方法可以处理周期性的更新.

  1. 视图周期性的回调action creator
  2. action creator返回一个thunk对象,这个对象周期性的dispatch TICK actions.
解决方案1:让视图dispatch更新

对于#1方案,视图必须等待定时器的状态从停止转变为开始才能开始周期性的action派发.意思是我们不得不使用有状态的组件.

class Timer extends Component {
  componentWillReceiveProps(nextProps) {
    const { state: { status: currStatus } } = this.props;
    const { state: { status: nextStatus } } = nextProps;

    if (currState === 'Stopped' && nextState === 'Running') {
      this._startTimer();
    } else if (currState === 'Running' && nextState === 'Stopped') {
      this._stopTimer();
    }
  }

  _startTimer() {
    this._intervalId = setInterval(() => {
        this.props.tick();
    }, 1000);
  }

  _stopTimer() {
    clearInterval(this._intervalId);
  }

  // ...
}

这种处理方式可以工作,但是这会使视图变得满是状态,而且也会不纯净.另一个问题是我们的组件现在不仅仅需要渲染HTML,捕获用户的交互操作还要承担更多的工作.这种方式里引入致异步操作会使视图和应用作为一个整体,很难理清.在计时器这个app里面可能还不是什么问题.但是如果在一个大型的应用中,你可能想把异步操作放到整个应用的外面.

所以使用Thunks对象怎么样?

解决方案2:在Action Creator中使用Thunks对象

替代方案1在视图中进行操作,可以在我们的action creator中使用thunks.改变一下start的action creator

 export default {
  start: () => (
    (dispatch, getState) => {
      // This transitions state to Running
      dispatch({ type: 'START' });
      //上面的注释的译文:dispatch({type:'START'})改变状态为Running

      // Check every 1 second if we are still Running.
      // If so, then dispatch a `TICK`, otherwise stop
      // the timer.
      //每一秒种检测一下状态是不是还是Running,如果是的
      //话,dispatch ‘TICK’aciton.否则就停止计时器
      const intervalId = setInterval(() => {
        const { status } = getState();

        if (status === 'Running') {
          dispatch({ type: 'TICK' });
        } else {
          clearInterval(intervalId);
        }
      }, 1000);
    }
  )
  // ...
};

Start action creator将会dispatch一个START action,只要start回调函数被调用.接着只要计时器只要还在工作,每一秒钟将会dispatch一个TICK action.
在action creator中使用的方式一个问题是action creator现在要做很多的事情.测试也是一个很难完成的任务,因为没有返回任何数据.

最好的解决办法是:使用Sagas去管理计时器.

redux-sagas重新定义side-effects为Effects.Effects由Sagas生成.sagas的概念据我所知来自CQRS和Event Sourcing世界.有许多讨论争论sagas到底是什么,但是你可以认为sagas是和系统交互的永久线程:

  1. 对系统中的acion dispach做出反应
  2. 往系统中Dispatch新的actions
  3. 可以使用内部机制在没有外部actions的情况下自我复苏.例如周期性的苏醒.

在redux-saga里,一个saga就是一个生成器函数(generator function),可以在系统内无限期运行.当特定action被dispatch时,saga就可以被唤醒.saga也可以继续dispatch额外的actions,也可以接入程序的单一状态树.
例如,我们想在计时器运行的时候,周期性的dispatch TICKS.看看下面的操作:

  function* runTimer(getState) {
  // The sagasMiddleware will start running this generator.
  //sagas中间件将开始运行这个生成器函数.
  
  

  // Wake up when user starts timer.
  //当用户开始计时器的时候唤醒.
  while(yield take('START')) {
    while(true) {
      // This side effect is not run yet, so it can be treated
      //side effect 没有运行,所以可以看做数据
      // as data, making it easier to test if needed.
      //这样测试比较容易一点
      yield call(wait, ONE_SECOND);

      // Check if the timer is still running.
      //检测计时器是否运行
      // If so, then dispatch a TICK.
      //如果计时器运行的话,就dispatch一个TICK
      if (getState().status === 'Running') {
        yield put(actions.tick());
      // Otherwise, go idle until user starts the timer again.
      //如果计时器没有运行的话,就进入休眠状态等待计时器的重新工作
      } else {
        break;
      }
    }
  }
}

正如你所见到的,一个saga使用普通的JavaScript控制流程来构建协作side-effects和action creators的过程.take函数在START action被dispatch的时候唤醒.call函数允许我们创建类似于待办事项的等待效果.(就是类似list-todo,已经在日程表中列出,但还没有执行的任务)
通过使用saga,我们可以保持视图和action creator成为纯函数.saga使我们可以使用类似javascript构造函数的方式创建state转变的模型.

包装

Sagas是系统内管理side-effects的途径.当你的应用中需要长时间运行的进程来协作多个action creators和side-effects的时候,Sagas将会非常的合适.
Sagas不仅对actions做出响应,而且对内部机制也可以做出响应(例如,时间依赖的effects).Sagas尤其有用,特别是你需要在正常的Flux流程之外管理side-effects的时候.例如,一个用户的交互操作可能会有更多的action产生,但是这些actions却不需要用户更多的操作.
最后,当你需要一个无限状态机模型的时候,sagas也值得一试.

如果你想看看Timer app的完整代码,看看这里.

你准备尝试sagas了吗?好了,有什么想法呢?

相关文章

  • 在React/Redux应用中使用Sagas管理异步操作(翻译)

    在React/Redux应用中使用Sagas管理异步操作 参考这篇文章,译文可能先幼稚,参考看看吧.redux-s...

  • 关于队列的生活实例-redux-sagas异步操作(1)

    讲在redux-sagas异步操作之前,算是铺垫。举个银行的排队例子。 看了很多的队列实例,都没举个简单例子。 其...

  • Redux-saga

    Redux-saga 概述 redux-saga是一个用于管理redux应用异步操作的中间件,redux-saga...

  • 一起来学点redux-saga

    1.概述 Redux-saga是一个用于管理 Redux 应用异步操作的中间件(又称异步action) 本质都是为...

  • React中的Redux

    学习必备要点: 首先弄明白,Redux在使用React开发应用时,起到什么作用——状态集中管理 弄清楚Redux是...

  • redux-saga

    redux-saga 一个用于管理Redux应用异步操作的中间件,使副作用(数据获取、浏览器缓存获取)易于管理、运...

  • react依赖注入之mapStateToProps&&mapDi

    在react-redux开发中每个模块有自己的state用来统一管理视图数据。在使用react-redux之后,可...

  • react-redux

    redux 全局状态管理 react-redux 优化模块 优化redux的使用过程 通过react-redux ...

  • 11.项目实战:Header组件开发(三)

    使用React-Redux进行应用数据的管理 我们想引入Redux进行数据管理,顺便把之前的focused也放进去...

  • redux

    redux是一个状态管理库,他并不是react的插件,只是在react中应用的比较多 根据结构图来看,redux有...

网友评论

本文标题:在React/Redux应用中使用Sagas管理异步操作(翻译)

本文链接:https://www.haomeiwen.com/subject/mrftgttx.html