redux-saga简介

redux-saga GitHub地址

https://github.com/yelouafi/redux-saga

redux-saga 官方文档

文档地址

redux-saga 简介

一种专门用来处理异步请求(例如:fetch)的redux插件

使用方式

npm install --save redux-saga

当然也支持UMD的加载方式

https://unpkg.com/redux-saga/dist/redux-saga.js
https://unpkg.com/redux-saga/dist/redux-saga.min.js

以前的方法有什么缺点

通过redux-thunk使用action创造器做一些流程控制的事情。这样可能会很难测试,但是管用。

// an action creator with thunking
function createRequest () {
  return function (dispatch, getState) {
    dispatch({ type: 'REQUEST_STUFF' })
    someApiCall(function(response) {
      // some processing
      dispatch({ type: 'RECEIVE_STUFF' })
    }
  }
}

可能在组件中还会写到

function onHandlePress () {
  this.props.dispatch({ type: 'SHOW_WAITING_MODAL' })
  this.props.dispatch(createRequest())
}

使用redux状态树(和reducers)来扮演一个信号系统,把东西链接在一起。 到处都是代码

Saga长什么样子?

Saga其实是generator生成器函数

function *hello() {
}

对thunk的改进

function *hello() {
  yield take('BEGIN_REQUEST')
  yield put({ type: 'SHOW_WAITING_MODAL' })
  const response = yield call(myApiFunctionThatWrapsFetch)
  yield put({ type: 'PRELOAD_IMAGES', response.images })
  yield put({ type: 'USAGE_TRACK', activity: 'API_CALL'})
  yield put({ type: 'HIDE_WAITING_MODAL' })
}

示例

[图片上传失败...(image-d8de54-1527745550799)]

如果简单的用react-redux实现,缺点是:组件视图里面会混入一些应用逻辑代码

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

  // ...
}

如果采用redux-thunk,我们可以这样写,缺点是:简单地将应用逻辑引入到了action中,导致action过于复杂,并且不利于测试

export default {
  start: () => (
    (dispatch, getState) => {
      // This transitions state to Running
      dispatch({ type: 'START' });

      // Check every 1 second if we are still Running.
      // If so, then dispatch a `TICK`, otherwise stop
      // the timer.
      const intervalId = setInterval(() => {
        const { status } = getState();

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

采用redux-saga,就可以完美地单独构建应用逻辑

function* runTimer(getState) {
  // The sagasMiddleware will start running this generator.

  // Wake up when user starts timer.
  while(yield take('START')) {
    while(true) {
      // This side effect is not run yet, so it can be treated
      // 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.
      if (getState().status === 'Running') {
        yield put(actions.tick());
      // Otherwise, go idle until user starts the timer again.
      } else {
        break;
      }
    }
  }
}

示例源码地址
推荐文章

常用API

初始化API

Middleware API

createSagaMiddleware(options)

用于创建一个中间件并连接saga和redux。

middleware.run(saga, ...args)

启动saga,此方法能自动检测yield的执行,当前yield返回时,会执行next(result),result是上一个yield返回的结果。一般saga的Generator函数会写成try/catch的形式,当执行出错是就会执行catch。如果saga在中途被cancel掉了,就会执行Generator里的return。

上层API
takeEvery(pattern, saga, ...args)

相当于redux-thunk的执行模式,响应匹配到的状态多少次,执行多少次,但是不会根据响应的次序返回response。

takeLatest(pattern, saga, ...args)

如果上一次异步响应还没有返回,此时又接收到一次匹配的状态,则会cancel掉前一次的请求,去执行最新的一次请求。


##### 底层API
take(pattern)

相对于takeEvery,更底层,但是更实用,实例代码如下:

function* watchAndLog() {
  yield takeEvery('*', function* logger(action) {
    const state = yield select()

    console.log('action', action)
    console.log('state after', state)
  })
}
function* loginFlow() {
  while (true) {
    yield take('LOGIN')
    // ... perform the login logic
    yield take('LOGOUT')
    // ... perform the logout logic
  }
}

使用Channels

import { take, fork, ... } from 'redux-saga/effects'

function* watchRequests() {
  while (true) {
    const {payload} = yield take('REQUEST')
    yield fork(handleRequest, payload)
  }
}

function* handleRequest(payload) { ... }

actionChannel

actionChannel用来接收redux store内部状态消息

import { take, actionChannel, call, ... } from 'redux-saga/effects'

function* watchRequests() {
  // 1- Create a channel for request actions
  const requestChan = yield actionChannel('REQUEST')
  while (true) {
    // 2- take from the channel
    // const requestChan = yield actionChannel('REQUEST', buffers.sliding(5))
    const {payload} = yield take(requestChan)
    // 3- Note that we're using a blocking call
    yield call(handleRequest, payload)
  }
}

function* handleRequest(payload) { ... }

eventChannel

eventChannel用来接收redux store之外的消息

import { take, put, call, cancelled } from 'redux-saga/effects'
import { eventChannel, END } from 'redux-saga'

// creates an event Channel from an interval of seconds
function countdown(seconds) { ... }

export function* saga() {
  const chan = yield call(countdown, value)
  try {    
    while (true) {
      let seconds = yield take(chan)
      console.log(`countdown: ${seconds}`)
    }
  } finally {
    if (yield cancelled()) {
      chan.close()
      console.log('countdown cancelled')
    }    
  }
}

channel

channel用在多个saga之间通信

import { channel } from 'redux-saga'
import { take, fork, ... } from 'redux-saga/effects'

function* watchRequests() {
  // create a channel to queue incoming requests
  const chan = yield call(channel)

  // create 3 worker 'threads'
  for (var i = 0; i < 3; i++) {
    yield fork(handleRequest, chan)
  }

  while (true) {
    const {payload} = yield take('REQUEST')
    yield put(chan, payload)
  }
}

function* handleRequest(chan) {
  while (true) {
    const payload = yield take(chan)
    // process the request
  }
}

你可能感兴趣的:(redux-saga简介)