Redux-Saga的理解与实现

2019-07-21  本文已影响0人  吴晗君

基础流程

在saga中需要对不同的业务写不同的generator函数,然后通过take(redux-saga暴露的方法)监听对应的action-type,内部是通过发布订阅存储在内存中。订阅的回调函数就是内部的一个next方法,这个next的作用是:通过generator生成的iterator中的next方法来持续迭代generator函数。

当组件中dispatch对应的action type的时候,redux-saga中间件就会publish(action type),来执行订阅函数。这个时候,就会执行对应generator的next方法。比如这时候就去请求数据,也是通过yield API.getSomething()的写法来拿到数据,然后在下一步通过put来发出修改redux中store数据的action。执行put的时候,会在next函数中直接dispatch出一个真实的action,来改变仓库中的数据。

知识点

主要点在于是在yield的用法,以及如何通过发布订阅与React组件、yield函数执行发生联系。以及对多种情况的公共处理:take、takeEvery、put、delay、fork、call、cps、cancel。

用法

import { put, all, call, take, takeEvery, fork, cancel } from "../../redux-saga/effects"
import * as types from '../action-types'

function delay (ms) {
  return new Promise((resolve) => setTimeout(resolve, ms, ms))
}

// function* incrementAsync () {
//   yield call(delay, 1000)
//   yield put({type: types.INCREMENT})
// }

function* watchIncrementAsync() {
  debugger
  // for (let i=0;i<3;++i) {
  //   // yield take(types.ASYNC_INCREMENT)
  //   // yield call(delay, 1000)
  //   yield put({type: types.INCREMENT})
  // }
  yield put({type: types.INCREMENT})
  console.log('所有generator函数已完成执行')
}

function* helloSaga (actionType) {
  console.log('logger', actionType)
  // const task = 
  // yield fork(task)
}

// 自执行每秒加1
function* increment () {
  while(true){
    console.log('22222')
      yield delay(1000) 
      console.log('11111')
      yield put({type:types.INCREMENT})
  }
}

function* incrementWatcher () {
  const task = yield fork(increment)
  console.log('incrementWatcher')
  yield take(types.CANCEL_INCREMENT)
  console.log('take')
  yield cancel(task)
}

export default function* rootSaga () {
  console.log(666666)
  // debugger
  // yield watchIncrementAsync()
  yield incrementWatcher()
  // all([
  //   incrementWatcher()
  //   // takeEvery(types.ASYNC_INCREMENT, helloSaga)
  // ])
  // yield takeEvery(types.ASYNC_INCREMENT, helloSaga)
  // yield takeEvery(types.ASYNC_INCREMENT, watchIncrementAsync)
  console.log('所有generator函数全都执行完毕')
}

实现

effect.js

export function call (fn, ...args) {
  return {
    type: 'CALL',
    fn,
    args
}
}

export function put (action) {
  return {
    type: 'PUT',
    action
  }
}

export function take (actionType) {
  return {
    type: 'TAKE',
    actionType
  }
}

// fork的作用是不阻塞generetor函数,再开一个generator函数来做对应的事情。
export function fork (task) {
  return {
    type: 'FORK',
    task
  }
}

export function* takeEvery (actionType, task) {
  yield fork(function* () {
    while(true) {
      yield take(actionType)
      yield task(actionType)
    }
  })
}

const innerDelay= ms => new Promise((resolve) => setTimeout(resolve, ms, ms))
export function delay(...args) {
  return call(innerDelay, ...args)
}
// 用以处理回调函数的形式
export function cps (fn, ...args) {
  return {
      type: 'CPS',
      fn,
      args
  }
}

export function all (fns) {
  return {
      type: 'ALL',
      fns
  }
}

export function cancel (task) {
  return {
      type: 'CANCEL',
      task
  }
}

createSagaMiddleware.js

const isIterator = (it) => typeof it[Symbol.iterator] === 'function'
const isPromise = (o) => typeof o.then === 'function'
const times = (cb, total) => {
  for (let i = 0; i < total; ++i) {
    if(i === total){
      cb()
    }
  }
}

export default function createSagaMiddleware () {
  function createChannel () {
    let _listeners = {}
    function subscribe (actionType, cb) {
      console.log(`订阅${actionType}`)
      _listeners[actionType] = _listeners[actionType] || []
      const once = () => {
        cb()
        off(actionType, once)
      }
      once._o = cb
      _listeners[actionType].push(once)
    }

    function publish(actionType) {
     const nexts =  _listeners[actionType]
     if (nexts && nexts.length) {
      nexts.forEach(next => next())
     }
    }

    function off (key, cb) {
      if (_listeners[key]) {
        _listeners[key] = _listeners[key].filter(l => l !== cb && l._o !== cb)
      }
    }
    return {
      subscribe,
      publish
    }
  }
  // 创建监听管道,其实就是发布订阅
  let channel = createChannel()
  const sagaMiddleware = ({dispatch, getState}) => {
    function run (gen, cb) {
      const it = isIterator(gen) ? gen : gen()
      function next (returnValue) {
        // effect是saga出得一个概念,指的是
        // 一些简单 Javascript 对象,包含了要被 middleware 执行的指令。 
        // 当 middleware 拿到一个被 Saga yield 的 Effect,它会暂停 Saga,
        // 直到 Effect 执行完成,然后 Saga 会再次被恢复。

        // next函数参数是上一次yield 返回值。
        const {value:effect, done} = it.next(returnValue)
        if (!done) {
          if (isIterator(effect)) {
            run(effect)
            next()
          } else if (isPromise(effect)) { // 处理yield + promise的情况
            effect.then(next)
          } else {
            const {type, actionType, action, task, fn, args, fns} =effect
            switch (type) {
              case 'TAKE': 
              // take会再yield的地方等待,
              // 等组件dispatch的时候,调用next执行监听的内容(写在take()后面的代码)。
                console.log('take----', actionType)
                channel.subscribe(actionType, next)
                break;
              case 'PUT':
                console.log('put----')
                dispatch(action)
                next() // put不阻塞
                break;
              case 'FORK':
                console.log('fork----')
                const newTask = task()
                run(newTask)
                next(newTask)
                break;
              case 'CALL': // call返回的都是promise
                  console.log('call----')
                  fn(...args).then(next)
                  break;
              case 'CPS':
                console.log('cps----')
                fn(...args, next)
                break;
              case 'ALL': // 直到所有all完成(所有generator函数调用next函数皆返回{d}one:true})
                console.log('all----')
                const done = times(next, fns.length)
                fns.forEach((fn) => run(fn, done))
                break;
              case 'CANCEL': // 直到所有all完成(所有generator函数调用next函数皆返回{d}one:true})
                console.log('cancel----', task)
                task.return('中断执行')
                break;
            }
          }
        } else {
          cb && cb()
        }
      }
      next()
    }
    // run放到sagaMiddleware函数里面的目的是需要拿到dispatch方法
    sagaMiddleware.run = run
    return (next) => (action) => {
      const type = action.type
      channel.publish(type)
      next(action)
    }
  }
  
  return sagaMiddleware
}
上一篇下一篇

猜你喜欢

热点阅读