redux-saga

153 阅读3分钟

redux-saga一种和redux-thunk一样能处理redux的异步action中间件

两者的区别:1.redux-thunk只用来解决副作用问题,普通的js函数源码实现

                    2.redux-saga 能解决副作用问题,程序更高效执行,易于测试,易于处理错误

                    3.redux-saga采用es6---Generator(生成器)函数语法,实现异步流程的控制管                       理,化异步为同步


Generator语法示例:

function * generator () {
  yield 'hello'
  yield 'world'
}

let gen = generator()

gen.next() // { value: 'hello', done: false}
gen.next() // { value: 'world', done: false}
gen.next() // { value: undefined, done: true}


generator是生成器函数,*:是它的专有标志。

yield是暂停标志,每次程序运行到yield时都会暂停,等待下一次指令的执行;它只能在generator函数里,后面跟着一个表达式。

return是终止标志。只有执行了next才会开始调用generator函数。next传入的参数会当作上一个yield表达式的返回值,所以第一次调用next传入的参数是无效的。

// 异步函数
function asy() {
    $.ajax({
        url: 'test.txt',
        dataType: 'text',
        success() {
            console.log("我是异步代码");
        }
    })
}

function* gener() {
    let asy = yield asy();
    yield console.log("我是同步代码");
}
let it = gener().next();
it.then(function() {
    it.next();
})
// 我是异步代码
// 我是同步代码

// 浏览器编译之后
function gener() {
    // let asy = yield asy(); 替换为
    $.ajax({
        url: 'test.txt',
        dataType: 'text',
        success() {
            console.log("我是异步代码");
            // next 之后执行以下
            console.log("我是同步代码");
        }
    })
    // yield console.log("我是同步代码");
}

整个过程类似于,浏览器遇到标识符 * 之后,就明白这个函数是生成器函数,一旦遇到 yield 标识符,就会将以后的函数放入此异步函数之内,待异步返回结果后再进行执行

通函数在被调用时,JS 引擎会创建一个栈帧,在里面准备好局部变量、函数参数、临时值、代码执行的位置(也就是说这个函数的第一行对应到代码区里的第几行机器码),在当前栈帧里设置好返回位置,然后将新帧压入栈顶。待函数执行结束后,这个栈帧将被弹出栈然后销毁,返回值会被传给上一个栈帧。

当执行到 yield 语句时,Generator 的栈帧同样会被弹出栈外,但Generator在这里耍了个花招——它在堆里保存了栈帧的引用(或拷贝)!这样当 it.next 方法被调用时,JS引擎便不会重新创建一个栈帧,而是把堆里的栈帧直接入栈。因为栈帧里保存了函数执行所需的全部上下文以及当前执行的位置,所以当这一切都被恢复如初之时,就好像程序从原本暂停的地方继续向前执行了。

而因为每次 yield 和 it.next 都对应一次出栈和入栈,所以可以直接利用已有的栈机制,实现值的传出和传入。


saga作为中间件和thnuk一样需要注册

import { createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga'
import reducer from './reducers'
import mySaga from './sagas'

// create the saga middleware
const sagaMiddleware = createSagaMiddleware()
// mount it on the Store
const store = createStore(
  reducer,
  applyMiddleware(sagaMiddleware)
)

// then run the saga
sagaMiddleware.run(mySaga)

export default store

接下来新建一个saga.js文件运行saga

import { createActions } from 'redux-actions'
import { call, put, takeLatest } from 'redux-saga/effects'
import { fetchDataApi } from '@api/index'

export const {
  data: { fetchDataReq, fetchDataSucc, fetchDataFailed }
} = createActions({
  DATA: {
    FETCH_DATA_REQ: null,
    FETCH_DATA_SUCC: rsp => ({ data: rsp }),
    FETCH_DATA_FAILED: null,
  }
})

function* fetchDataReqSaga() {
  try {
    const rsp = yield call(fetchDataApi)
    yield put(fetchDataSucc(rsp))
  } catch (e) {
    yield put(fetchDataFailed(e))
  }
}

function* watchFetchSaga() {
  yield takeLatest(fetchDataReq, fetchDataReqSaga)
}

export default watchFetchSaga

redux-saga源码

// sagaMiddleware
export default function sagaMiddlewareFactory({ context = {}, ...options } = {}) {
  ...
  // sagaMiddleware.run其实调用的是runsage
  function sagaMiddleware({ getState, dispatch }) {
    const sagaEmitter = emitter()
    sagaEmitter.emit = (options.emitter || ident)(sagaEmitter.emit)
    // 为runSaga提供redux的函数以及subscribe
    sagaMiddleware.run = runSaga.bind(null, {
      context,
      subscribe: sagaEmitter.subscribe,
      dispatch,
      getState,
      sagaMonitor,
      logger,
      onError,
    })
    // 按照redux插件要求,返回一个函数
    return next => action => {
      const result = next(action) //  reducers
      sagaEmitter.emit(action)
      return result
    }
  }
  ...
}