web前端开发|redux-saga简易实现

index.js:

import { channel } from './channel'//promise判断 const isPromise = p => { return typeof p.then == 'function' && typeof p.catch == 'function' }function createSagaMiddelware() { function sagaMiddelware({getState,dispatch}){ //负责把gernerator执行完毕 function run(iterator){ //执行得到迭代器,next得到值,可能是器也可能是迭代器 let it = typeof iterator == 'function'?iterator():iterator; function next(input){ let {value: effect,done}=it.next(input); //如果迭代器没有完成 if(!done){ //genrator if(typeof effect[Symbol.iterator] == 'function'){ run(effect); next(); } //延迟函数 if(isPromise(effect)) { effect .then(next) .catch(error => next(error)) } switch(effect.type){ //注册事件 case 'take': let {actionType}=effect; channel.subscribe(actionType,next); break; //走到put的事件就直接dispatch执行了 case 'put': let {action}=effect; dispatch(action); next(action); break; //fork继续执行 case 'fork': let {worker}=effect; run(worker); next(); break; //异步执行成功后再next case 'call': let {fn,args}=effect; fn(...args).then(next); break; default: break; } } } next() } sagaMiddelware.run = run; //中间件执行 return (next) => (action) => { next(action) channel.publish(action) } } return sagaMiddelware; } export default createSagaMiddelware;

channel.js
function createChannel() { //对象每一个动作对应一个回调函数 let takers={}; function subscribe(actionType,cb) { takers[actionType]=cb; } function publish(action) { //监听 let taker=takers[action.type] if (taker) {//如果有执行监听函数并且删除监听函数 let tmp=taker; taker = null; tmp(action); } } return {subscribe,publish}; } export let channel = createChannel();

effect.js
function take(actionType) { return { type: 'take', actionType } } function put(action) { return { type: 'put', action } } function fork(worker) { return { type: 'fork', worker } } function call(fn,...args) { return { type: 'call', fn, args } } //监听每一个动作类型,当此动作发生的时候执行对应的worker //takeEvery它会单开一个任务,并不会阻塞当前saga function* takeEvery(actionType,worker) { yield fork(function* () { while (true) { let action = yield take(actionType); yield worker(action); } }) }function delay(ms, val = true) { let timeoutId const promise = new Promise(resolve => { timeoutId = setTimeout(() => resolve(val), ms) })promise['CANCEL_PROMISE'] = () => clearTimeout(timeoutId)return promise }//takerEvery的结果是一个迭代器 //iterator export { take, put, takeEvery, call, delay }

使用:
app.js:
import React from 'react'; import ReactDOM from 'react-dom'; import Root from './router'; import {createStore, applyMiddleware } from 'redux'; import { Provider } from 'react-redux'; import createSagaMiddleware from './testSaga/index.js' // import createSagaMiddleware from './saga/index' import { watchIncrementAsync, watchAndLog } from './sagaActions/index'//globe css import './style/index.styl'; import './style/less.less'; import './style/sass.sass'; import './style/scss.scss'; const initialState = { number: 0, list: [] }; const incrementReducer = (state = initialState, action) => { switch(action.type) { case 'INCREMENT': { state.number += 1 return { ...state } break } case 'DECREMENT': { return { ...state, list: action.data.data } break }; default: return state; } }; const sagaMiddleware = createSagaMiddleware(); const store = createStore(incrementReducer,applyMiddleware(sagaMiddleware)) sagaMiddleware.run(watchIncrementAsync) // sagaMiddleware.run(watchAndLog)ReactDOM.render( , document.getElementById('app') );

sagaAction:
// import { delay } from '../saga' // import { select, call, fork, take, put, takeEvery } from '../saga/effects' import { call, put, takeEvery, take, delay } from '../testSaga/effect' import {GetUserData} from '../fetch/api.js'export function* watchAndLog() { while (true) { const action = yield take('*') const state = yield select()console.log('action', action) console.log('state after', state) } }export function* incrementAsync() { yield delay(1000) yield put({ type: 'INCREMENT' }) }export function* indecrementAsyncs({ payload }) { //发起请求payload是给请求函数的参数 const data = https://www.it610.com/article/yield call(GetUserData, payload); yield put({ type:'DECREMENT', data }) }//发起请求 function* fetchUrl(param) { const data = https://www.it610.com/article/yield call(GetUserData, param); // 指示中间件调用 fetch 异步任务 yield put({ type:'DECREMENT', data }); // 指示中间件发起一个 action 到 Store }export function* watchIncrementAsync() { yield takeEvery('INCREMENT_ASYNC', incrementAsync) yield takeEvery('DECREMENT_ASYNC', indecrementAsyncs) //或者 // while(true) { //const action = yield take('FETCH_REQUEST'); // 指示中间件等待 Store 上指定的 action,即监听 action //yield fork(fetchUrl, action.payload); // 指示中间件以无阻塞调用方式执行 fetchUrl // } }

发起事件:
onClick() { this.props.dispatch({ type: 'DECREMENT_ASYNC', // type: 'FETCH_REQUEST', //参数的传递 payload: { name: 'test', s:11 } }) }

执行流程分析:
初始化阶段:
执行createSagaMiddelware 返回 sagaMiddelware
执行 saga run方法 传入的是自定义的 watchIncrementAsync函数:这里面是takeEvery命令
拿到 iterator (watchIncrementAsync)并执行一下
初次执行一下 next 函数,此时next中 input参数为undefined
执行generator 的 next 获取 watchIncrementAsync中 第一个yield(takeEvery类型)的值
如果里面是iterator则继续run next 最终走到 fork ,fork里面因为是generator函数 则继续执行 run next
走到 type take 往channel监听器中添加 actionType 和 netx函数回调 这样dispatch 就可以执行这个next回调
初始化阶段完毕
【web前端开发|redux-saga简易实现】点击dispatch执行阶段:
dispatch takeEvery中的类型 找到初始化阶段添加注册的此类型并执行 netx 回调
然后it.next(input) 触发的是 yield worker(action); worker就是incrementAsync函数因为他是generator函数 所以会继续执行run()后 再next就可以继续yield
如果其中有 自定义 delay 延迟函数,则等待then结束再回调 next
worker此时是逻辑执行函数如果是
yield put 就直接执行 dispatch(action); 并且next一下,后面可能有多个yield
yield call 执行fn(…args).then(next);

    推荐阅读