React | 2020-06-26 13:23:51 516次 1次
Redux-Saga 依赖于 generator 函数,本质上是 redux 的一个中间件,内部亦是发布订阅的方式来执行事件。它是一个用于管理应用程序 Side Effect(副作用,例如异步获取数据,访问浏览器缓存等)的 library,它的目标是让副作用管理更容易,执行更高效,测试更简单,在处理故障时更容易。
模拟实现这个功能时需要考虑如下几点:
1. 如何实现任务的发布订阅
2. 如何让 generator 函数自执行
3. 一些 effect 指令和辅助函数设计
4. saga 中间件如何组织代码
一、channel 实现
function createChannel() { //对象每一个动作对应一个回调函数 let takers={}; function subscribe(actionType, cb) { takers[actionType] = cb; } function publish(action) { let taker=takers[action.type] if (taker) { let tmp=taker; // 避免重复执行被监听的事件 delete takers[action.type]; tmp(action); } } return {subscribe,publish}; } export let channel = createChannel();
二、effect 指令和辅助函数
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的结果是一个迭代器 export { take, put, takeEvery, call, delay }
三、generator执行和指令执行
这一步要依据中间件代码格式进行扩展补充,多一个 publish 操作。
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;
四、简单使用
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( <Provider store={store}> <Root /> </Provider>, 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 = yield call(GetUserData, payload); yield put({ type: 'DECREMENT', data }) } //发起请求 function* fetchUrl(param) { const data = 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 } }) }
把 saga 往最简单的方面想,就是 generator 中的 yield 流程控制,至于后面跟的是 take 指令还是 put 或其他指令,再去找对应的分支处理即可。归根结底就是 take 和 put 的过程。
1人赞