Redux-Saga 原理浅析

React | 2020-06-26 13:23:51 354次 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人赞

分享到: