redux-logic原始碼閱讀

小宋發表於2019-01-24

在用ReactRedux做開發時, 都會用到非同步的一些東西, 之前更多的用的是redux-thunk或者redux-saga之類的, 但是都有用的不順的地方, 有一次突然發現redux-logic是一個很不錯的解決方案, 用起來也感覺很順手, 與市面上其他redux中介軟體不同的分析都在這裡, 感興趣的可以自己檢視。

首先我們來看下redux-logic的基本用法:


//	logic/index.js
import { createLogic } from `redux-logic`;

const someLogic = createLogic({
	//	當前logic監聽的actionType
	type: `SOME_ACTION_TYPE`,

	//	取消當前logic執行的actionType
	cancelType: `CANCEL_TYPE`,

	//	是否獲取最後一個返回
	latest: true,

	//	當前actionType的業務邏輯
	async process({ getState, action, cancelled }, dispatch, done) {
		const res = await asyncFn();
		dispatch(newAction({
			...res
		}));
		done();
	}
});

export someLogic;

//	store/index.js
import { createStore, applyMiddleware } from `redux`;
import { createLogicMiddleware } from `redux-logic`;
import { routerMiddleware } from `react-router-redux`;

import reducer from `../reducers`;
import history from `../history`;
import { someLogic } from `../logic`;

const configureStore = () => {
    const reduxRouteMiddleware = routerMiddleware(history),

		//	將所有的logic應用到中介軟體中
    	loggicMiddleware = createLogicMiddleware([someLogic]),
    	middlewares = process.env.NODE_ENV === `development` ? [loggicMiddleware, reduxRouteMiddleware, logger] : [loggicMiddleware, reduxRouteMiddleware],
        createStoreWithMiddleware = applyMiddleware(...middlewares)(createStore),
        store = createStoreWithMiddleware(reducer);
    return store;
};

export default configureStore();


複製程式碼

本文會逐步分析redux-logic的相關實現, 首先把redux-logic克隆到本地, 原始碼都位於redux-logic/src裡面, 所以我們從src/index.js開始:

import createLogic, { configureLogic } from `./createLogic`;

import createLogicMiddleware from `./createLogicMiddleware`;

export {
  configureLogic,
  createLogic,
  createLogicMiddleware
};

export default {
  configureLogic,
  createLogic,
  createLogicMiddleware
};

複製程式碼

從上面的程式碼中可以看到, 一共匯出3個函式, configureLogic的功能就是對所有的logic進行配置(超時警告時間等), 我們主要看createLogiccreateLogicMiddleware:

  • createLogic

createLogic位於src/createLogic

/**
 * 建立一個Logic物件
 * @param  {Object} logicOptions
 *         @param  {String}     logicOptions.name           可選值, 主要用於Logic中的異常提示, 可選值
 *         @param  {String}     logicOptions.type           觸發當前Logic的redux action type
 *         @param  {String}     logicOptions.cancelType     取消執行當前Logic的redux action type
 *         @param  {Boolean}    logicOptions.latest         是否只獲取最後一次的結果,類似redux-saga中的takeLatest effect
 *         @param  {Number}     logicOptions.debounce       函式去抖配置, 單位為毫秒
 *         @param  {Object}     logicOptions.throttle       函式節流配置, 單位為毫秒
 *         @param  {Function}   logicOptions.validate       在執行process之前的一個鉤子, 可以對當前action執行一些操作
 *         @param  {Function}   logicOptions.transform      validate的一個別名, validate和transform只需指定一個即可
 *         @param  {Function}   logicOptions.process        當前redux action type對應的處理邏輯(發起非同步請求, 在非同步請求返回成功之後觸發新的redux action)
 *         @param  {Object}     logicOptions.processOptions process中需要的一些配置
 *         @param  {Number}     logicOptions.warnTimeout    超時警告時間, 預設60秒, 需要在process中手動呼叫done來終止這個Logic, 如果是一個持續性的Logic, warnTimeout需要設定成0
 * @return {Object}              建立出來的Logic
 */
export default function createLogic(logicOptions = {}) {
    //  無效配置項驗證, 把無效的配置項鍵名放陣列返回
    const invalidOptions = getInvalidOptions(logicOptions, allowedOptions);
    if (invalidOptions.length) {
        throw new Error(`unknown or misspelled option(s): ${invalidOptions}`);
    }

    //  name, type, cancelType, validate, transform都從傳入的logicOptions裡面獲取
    //  如果其他配置項沒有在logicOptions中宣告, 就從預設配置中獲取或者給一個預設值
    const {
        name,
        type,
        cancelType,
        warnTimeout = defaultOptions.warnTimeout,
        latest = defaultOptions.latest,
        debounce = defaultOptions.debounce,
        throttle = defaultOptions.throttle,
        validate,
        transform,
        process = emptyProcess,
        processOptions = {}
    } = logicOptions;

    //  type必傳
    if (!type) {
        throw new Error(`type is required, use `*` to match all actions`);
    }

    //  validate和tranform只能同時指定一個
    if (validate && transform) {
        throw new Error(`logic cannot define both the validate and transform hooks they are aliases`);
    }

    //  warnTimeout需要放在processOptions同級
    if (typeof processOptions.warnTimeout !== `undefined`) {
        throw new Error(`warnTimeout is a top level createLogic option, not a processOptions option`);
    }

    //  獲取processOptions中的無效配置項
    const invalidProcessOptions = getInvalidOptions(processOptions, allowedProcessOptions);
    if (invalidProcessOptions.length) {
        throw new Error(`unknown or misspelled processOption(s): ${invalidProcessOptions}`);
    }

    //  如果validate和transform都沒傳入,就用預設的, 否則就用傳入的validate
    const validateDefaulted = (!validate && !transform) ?
        identityValidation :
        validate;

    //  如果在processOptions裡面指定了dispatchMultiple, warnTimeout應該是0
    if (NODE_ENV !== `production` &&
        typeof processOptions.dispatchMultiple !== `undefined` &&
        warnTimeout !== 0) {
        console.error(`warning: in logic for type(s): ${type} - dispatchMultiple is always true in next version. For non-ending logic, set warnTimeout to 0`);
    }

    /**
        根據process.length可以獲取傳入的process對應的處理函式中的形參個數
        從而確定processOption中的一些預設值

        const fn = function(arg1, agr2) {};
        console.log(fn.length);  -> 2

        const fn = function(arg1, agr2, arg3) {};
        console.log(fn.length); -> 3
    **/
    switch (process.length) {
        //  如果沒有或只有一個形參沒有傳入且dispatchReturn沒有在processOptions傳入, 就把它設定成true
        case 0:
        case 1:
            setIfUndefined(processOptions, `dispatchReturn`, true);
            break;

        //  兩個形參(single-dispatch模式[已廢棄])
        case 2:
            if (NODE_ENV !== `production` &&
                !processOptions.dispatchMultiple &&
                warnTimeout !== 0) {
                console.error(`warning: in logic for type(s): ${type} - single-dispatch mode is deprecated, call done when finished dispatching. For non-ending logic, set warnTimeout: 0`);
            }
            break;

        /**
            3個形參及更多, 認為是multi-dispatch模式
            processOptions.dispatchMultiple = processOptions === undefined ? true : processOptions.dispatchMultiple
        **/
        case 3:
        default:
            setIfUndefined(processOptions, `dispatchMultiple`, true);
            break;
    }

    //  返回處理好的物件
    return {
        name: typeToStrFns(name),
        type: typeToStrFns(type),
        cancelType: typeToStrFns(cancelType),
        latest,
        debounce,
        throttle,
        validate: validateDefaulted,
        transform,
        process,
        processOptions,
        warnTimeout
    };
}
複製程式碼

從上面的分析中我們可以得出: 在createLogic中做的主要是一些引數的驗證和預設值的處理, 下面我們一起看看他裡面的呼叫的一些的實現:

先來看下getInvalidOptions:

   function getInvalidOptions(options, validOptions) {
	  return Object.keys(options)
	    .filter(k => validOptions.indexOf(k) === -1);
   }

	/**	比如在createLogic裡的第一行就呼叫了該方法

	export default function createLogic(logicOptions = {}) {
	  const invalidOptions = getInvalidOptions(logicOptions, allowedOptions);
	  //	...
	}

	
	const allowedOptions = [
	  `name`,
	  `type`,
	  `cancelType`,
	  `latest`,
	  `debounce`,
	  `throttle`,
	  `validate`,
	  `transform`,
	  `process`,
	  `processOptions`,
	  `warnTimeout`
	];
	
	**/
複製程式碼

從上面的分析中我們可以看出, getInvalidOptions用來獲取Object裡的不合法的配置項, 並把非法的key作為陣列的形式返回。

typeToStrFns

//	如果是陣列形式就針對陣列的每一項都呼叫typeToStrFns, 返回一個新陣列
//	如果是函式形式就返回函式體的字串形式
//	其它直接返回
function typeToStrFns(type) {
  if (Array.isArray(type)) { return type.map(x => typeToStrFns(x)); }
  return (typeof type === `function`) ?
    type.toString() :
    type;
}
複製程式碼

setIfUndefined

function setIfUndefined(obj, propName, propValue) {
  if (typeof obj[propName] === `undefined`) {
    obj[propName] = propValue;
  }
}
複製程式碼

沒什麼好說的?

  • createLogicMiddleware
    createLogic分析完了, 下面一起看看createLogicMiddleware, createLogicMiddleware位於src/createLogicMiddleware裡:
/**
	@param arrLogic  Array.<logic>
	@param deps 	 Object
**/
export default function createLogicMiddleware(arrLogic = [], deps = {}) {
  if (!Array.isArray(arrLogic)) {
    throw new Error(`createLogicMiddleware needs to be called with an array of logic items`);
  }

	//	判斷是否有重複的logic
  const duplicateLogic = findDuplicates(arrLogic);
  if (duplicateLogic.length) {
    throw new Error(`duplicate logic, indexes: ${duplicateLogic}`);
  }

	/**
		因為redux-logic整合了rxjs
		所以Subject和BehaviorSubject都是rxjs裡的主體
		BehaviorSubject作為Subject的一個變體, 和Subject不同的是它有一個初始值
		https://cn.rx.js.org/manual/overview.html#h15
	**/
  const actionSrc$ = new Subject();

	//	用來監視所有action
  const monitor$ = new Subject();
  const lastPending$ = new BehaviorSubject({ op: OP_INIT });

	//	對monitor$使用累加器函式,返回生成的中間值,可選的初始值
  monitor$
    .scan((acc, x) => { // 追加一個pending狀態的計數器
      let pending = acc.pending || 0;
      switch (x.op) {
        case `top` : 		// 當前action位於logic棧的頂級
        case `begin` : 		// 開始觸發一個action
          pending += 1;
          break;

		/**
			在createLogic裡的process中呼叫了done, done的實現稍候分析;
		**/
        case `end`:
        case `bottom`: 			// action變成了一個被轉換後的新action

		/**
			在createLogic裡的validate中呼叫了allow, 且觸發了一個新的action
			craeteLogic({
				...
				validate({ getState, action }, allow, reject) {
					allow(action);
				}
			})
		**/
        case `nextDisp` :
        case `filtered` : 		// 	當前action由於無效被過濾
        case `dispatchError` : // 	派發action的時候異常(好像暫時沒用到)

		/**
			在action攔截器(validate)裡面執行reject
			craeteLogic({
				...
				validate({ getState, action }, allow, reject) {
					reject(action);
				}
			})
		**/
        case `cancelled`:
          pending -= 1;
          break;
      }
      return {
        ...x,
        pending
      };
    }, { pending: 0 })

	//	https://cn.rx.js.org/manual/usage.html
    .subscribe(lastPending$);

  let savedStore;
  let savedNext;
  let actionEnd$;
  let logicSub;
  let logicCount = 0;

	//	快取傳入的logic陣列
  let savedLogicArr = arrLogic;

	//	呼叫完createLogicMiddleware後返回的redux中介軟體
  function mw(store) {
	//	多次呼叫了createLogicMiddleware, 並且傳入了不同的store
    if (savedStore && savedStore !== store) {
      throw new Error(`cannot assign logicMiddleware instance to multiple stores, create separate instance for each`);
    }

	//	快取本次呼叫傳入的store
    savedStore = store;

    return next => {
      savedNext = next;

		//	從applyLogic返回中獲取action$, sub, 把logicCount賦值給cnt
      const { action$, sub, logicCount: cnt } =
            applyLogic(arrLogic, savedStore, savedNext,
                       logicSub, actionSrc$, deps, logicCount,
                       monitor$);
      actionEnd$ = action$;
      logicSub = sub;
      logicCount = cnt;

      return action => {
        debug(`starting off`, action);
        monitor$.next({ action, op: `top` });
        actionSrc$.next(action);
        return action;
      };
    };
  }

  //	給mw掛載一個指向monitor$的monitor$屬性
  mw.monitor$ = monitor$;

    /**
     * 一個自定義鉤子函式, 主要用於測試
     * @param  {Function} fn   可寫入相關測試邏輯
     * @return {Objetc}
     */
    mw.whenComplete = function whenComplete(fn = identity) {
        return lastPending$
            //  只有當x.pending > 0是才繼續往下走
            .takeWhile(x => x.pending)
            .map(( /* x */ ) => undefined)
            .toPromise()
            .then(fn);
    };

	/**
		給當前redux中介軟體動態新增依賴(相當於createLogicMiddleware第二個引數的擴充)
	**/
  mw.addDeps = function addDeps(additionalDeps) {
	//	所新增的依賴必須是一個物件型別
    if (typeof additionalDeps !== `object`) {
      throw new Error(`addDeps should be called with an object`);
    }

	//	遍歷所新增的中介軟體
    Object.keys(additionalDeps).forEach(k => {
      const existing = deps[k];
      const newValue = additionalDeps[k];
		//	所新增的中介軟體不能和當前已有的重名(當前已經有的不能被覆蓋)
      if (typeof existing !== `undefined` &&
          existing !== newValue) {
        throw new Error(`addDeps cannot override an existing dep value: ${k}`);
      }
      deps[k] = newValue;
    });
  };

	/**
		給當前redux中介軟體動態新增新的logic
		@param arrNewLogic Array.<Logic>
	**/
  mw.addLogic = function addLogic(arrNewLogic) {
    if (!arrNewLogic.length) { return { logicCount }; }

	//	合併到當前已有的陣列裡面
    const combinedLogic = savedLogicArr.concat(arrNewLogic);
    const duplicateLogic = findDuplicates(combinedLogic);

	//	判斷是否有重複
    if (duplicateLogic.length) {
      throw new Error(`duplicate logic, indexes: ${duplicateLogic}`);
    }
    const { action$, sub, logicCount: cnt } =
          applyLogic(arrNewLogic, savedStore, savedNext,
                     logicSub, actionEnd$, deps, logicCount, monitor$);
    actionEnd$ = action$;
    logicSub = sub;
    logicCount = cnt;
    savedLogicArr = combinedLogic;
    debug(`added logic`);
    return { logicCount: cnt };
  };

	/**
		給當前redux中介軟體合併新的logic
		@param arrNewLogic Array.<Logic>
	**/
  mw.mergeNewLogic = function mergeNewLogic(arrMergeLogic) {
    // 判斷是否重名
    const duplicateLogic = findDuplicates(arrMergeLogic);
    if (duplicateLogic.length) {
      throw new Error(`duplicate logic, indexes: ${duplicateLogic}`);
    }

    // 過濾掉重複的
    const arrNewLogic = arrMergeLogic.filter(x =>
      savedLogicArr.indexOf(x) === -1);
    return mw.addLogic(arrNewLogic);
  };

  /**
	替換當前所有的logic變成新的logic
   **/
  mw.replaceLogic = function replaceLogic(arrRepLogic) {
    const duplicateLogic = findDuplicates(arrRepLogic);

	//	判斷新的logic陣列裡是否有重複的logic
    if (duplicateLogic.length) {
      throw new Error(`duplicate logic, indexes: ${duplicateLogic}`);
    }
    const { action$, sub, logicCount: cnt } =
          applyLogic(arrRepLogic, savedStore, savedNext,
                     logicSub, actionSrc$, deps, 0, monitor$);
    actionEnd$ = action$;
    logicSub = sub;
    logicCount = cnt;
    savedLogicArr = arrRepLogic;
    debug(`replaced logic`);
    return { logicCount: cnt };
  };

  return mw;
}
複製程式碼

從上面的分析中我們可以看出, createLogicMiddleware返回了一箇中介軟體, 該中介軟體基於Rxjs實現, 返回的中介軟體中處理相關redux action對應的邏輯。

createLogicMiddleware中呼叫了一些其他函式, 逐個看下這些函式的實現:

findDuplicates, 該方法完成查詢陣列裡面重復的Logic, 並記錄重複的下標返回

/**
	@param arrLogic  Array.<Logic>   logic陣列
**/
/**
 * @param  {Array.<Logic>}  arrLogic Logic陣列
 * @return {Array.<Number>}          重複的Logic下標
 */
function findDuplicates(arrLogic) {
  return arrLogic.reduce((acc, x1, idx1) => {
    //  不是同一個下標, 且值相等的情況下就把下標放到acc裡面
    if (arrLogic.some((x2, idx2) => (idx1 !== idx2 && x1 === x2))) {
      acc.push(idx1);
    }
    return acc;
  }, []);
}
複製程式碼

再看看看applyLogic的實現:

/**
 * @param  {Array.<Logic>}   arrLogic        Logic陣列
 * @param  {Object}         store            redux store
 * @param  {Function}       next             redux中介軟體中的第二層函式
 * @param  {Rx.Subject}     sub              當前action對應的
 * @param  {Rx.Subject}     actionIn$        當前action對應的可訂閱物件
 * @param  {Object}         deps             createLogicMiddle的第二個引數
 * @param  {Number}         startLogicCount  用於命名
 * @param  {Rx.Subject}     monitor$         全域性可訂閱物件
 * @return {Object}
 */
function applyLogic(arrLogic, store, next, sub, actionIn$, deps, startLogicCount, monitor$) {
    if (!store || !next) { throw new Error(`store is not defined`); }

    //  如果當前Logic已經是一個Rx.Subject(已經被訂閱過了), 取消訂閱
    if (sub) { sub.unsubscribe(); }

    //  對當前Logic陣列進行操作(命名等), 返回一個新陣列
    const wrappedLogic = arrLogic.map((logic, idx) => {
        //  給當前未指定name的Logic進行命名並且返回, naming稍候分析
        const namedLogic = naming(logic, idx + startLogicCount);

        //  包裝命名後的Logic, wrapper稍候分析
        return wrapper(namedLogic, store, deps, monitor$);
    });

    const actionOut$ = wrappedLogic.reduce((acc$, wep) => wep(acc$), actionIn$);

    //  訂閱新的Observable物件
    const newSub = actionOut$.subscribe(action => {
        debug(`actionEnd$`, action);
        try {
            const result = next(action);
            debug(`result`, result);
        } catch (err) {
            console.error(`error in mw dispatch or next call, probably in middlware/reducer/render fn:`, err);
            const msg = (err && err.message) ? err.message : err;
            monitor$.next({ action, err: msg, op: `nextError` });
        }
        //  action變成了一個被轉換後的新action
        monitor$.next({ nextAction: action, op: `bottom` });
    });

    return {
        action$: actionOut$,
        sub: newSub,
        logicCount: startLogicCount + arrLogic.length
    };
}
複製程式碼

從上面的分析中我們可以看出, applyLogic返回了一個新的Rx.Observable, 裡面呼叫了namingwrapper, 下面我們逐個分析下。

先看naming的實現吧:

/**
 * 判斷當前傳入的Logic有沒有name, 有就不做任何操作直接返回, 沒有就給當前Logic新增一個name屬性後返回
 * @param  {Object} logic 當前Logic
 * @param  {Number} idx   當前Logic在arrLogic中的下標地址
 * @return {Object}
 */
function naming(logic, idx) {
    if (logic.name) { return logic; }
    return {
        ...logic,
        name: `L(${logic.type.toString()})-${idx}`
    };
}
複製程式碼

再看下wrapper的, wrapper是寫在src/logicWrapper裡的, wrapper返回的是一個Rx.Observable:

/**
 * Logic中相關處理
 * @param  {Object}     options.action   當前action
 * @param  {Object}     options.logic    當前logic
 * @param  {Object}     options.store    redux store
 * @param  {Object}     options.deps     createLogicMiddleware的第二個引數
 * @param  {Rx.Subject} options.cancel$  取消Logic執行的訂閱物件
 * @param  {Rx.Subject} options.monitor$ 全域性可訂閱物件
 * @return {Rx.Observable}
 */
export default function createLogicAction$({ action, logic, store, deps, cancel$, monitor$ }) {

    //  reduxStore.getState()
    const { getState } = store;

    //  從當前logic中取得相關配置引數
    const {
        name,
        warnTimeout,
        process: processFn,
        processOptions: {
            dispatchReturn,
            dispatchMultiple,
            successType,
            failType
        }
    } = logic;

    //  當前Logic的攔截器
    const intercept = logic.validate || logic.transform;

    debug(`createLogicAction$`, name, action);

    //  開始本次action的執行
    monitor$.next({ action, name, op: `begin` });

    /**
        1.當前action發生改變
        2.在validate/transform中呼叫allow
        3.無效的action type
        4.action被取消
        interceptComplete都會變成true, 標記攔截器處理完成
     **/
    let interceptComplete = false;

    //  https://cn.rx.js.org/class/es6/Observable.js~Observable.html#instance-method-take
    const logicAction$ = Observable.create(logicActionObs => {
            //  建立一個主題(只發出一個值), 用來訂閱`取消Logic執行的訂閱物件`, 在取消本次action後, 通知`取消Logic執行的訂閱物件`
            const cancelled$ = (new Subject()).take(1);
            cancel$.subscribe(cancelled$);
            cancelled$
                .subscribe(
                    () => {
                        //  確保cancel不會被呼叫2次(在createLogicMiddle中追加的pending只會被減一次)
                        if (!interceptComplete) {
                            monitor$.next({ action, name, op: `cancelled` });
                        } else {
                            monitor$.next({ action, name, op: `dispCancelled` });
                        }
                    }
                );

            //  如果當前Logic不是一個持續性的, 且沒有在warnTimeout / 1000秒內呼叫done(warnTimeout > 0), 就給出異常提示
            if (NODE_ENV !== `production` && warnTimeout) {
                Observable.timer(warnTimeout)
                    //  https://cn.rx.js.org/class/es6/Observable.js~Observable.html#instance-method-takeUntil
                    //  https://cn.rx.js.org/class/es6/Observable.js~Observable.html#instance-method-defaultIfEmpty
                    .takeUntil(cancelled$.defaultIfEmpty(true))
                    .do(() => {
                        //	console.error(`warning: logic (${name}) is still running after ${warnTimeout / 1000}s, forget to call done()? For non-ending logic, set warnTimeout: 0`);
                    })
                    .subscribe();
            }

            const dispatch$ = (new Subject())
                .mergeAll()
                .takeUntil(cancel$);

            dispatch$
                /**
                    .do({
                        nextOrObserver: mapToActionAndDispatch,
                        error: mapErrorToActionAndDispatch
                    })
                    這裡省略了nextOrObserver和error
                    https://cn.rx.js.org/class/es6/Observable.js~Observable.html#instance-method-do
                 **/
                .do(
                    mapToActionAndDispatch,
                    mapErrorToActionAndDispatch
                )
                .subscribe({
                    error: ( /* err */ ) => {
                        //  在發生異常後, 終止本次acion, 並且取消訂閱cancelled$
                        monitor$.next({ action, name, op: `end` });
                        cancelled$.complete();
                        cancelled$.unsubscribe();
                    },
                    complete: () => {
                        //  本次action處理完成
                        monitor$.next({ action, name, op: `end` });
                        cancelled$.complete();
                        cancelled$.unsubscribe();
                    }
                });

            //  觸發redux裡面的action
            function storeDispatch(act) {
                monitor$.next({ action, dispAction: act, op: `dispatch` });
                return store.dispatch(act);
            }

            /**
             * 適配不同情況的action, 組裝後如果是一個有效的redux action, 就呼叫reduxStore.dispatch
             * @param  {Object} actionOrValue
             */
            function mapToActionAndDispatch(actionOrValue) {
                /**
                    let act;
                    if (isInterceptAction(actionOrValue)) {
                        act = unwrapInterceptAction(actionOrValue);
                    } else {
                        if (successType) {
                            act = mapToAction(successType, actionOrValue, false);
                        } else {
                            act = actionOrValue;
                        }
                    }
                    把下面的程式碼拆成上面的樣子, 大概做了下面幾件事情:
                    判斷是不是一個攔截器, 如果是就把之前包裝的攔截器解包
                    判斷processOptions.successType是否存在, 存在就呼叫mapToAction, 拼出一個新的redux action, 呼叫reduxSrore.dispatch
                    否則就直接使用actionOrValue
                **/
                const act = (isInterceptAction(actionOrValue)) ? 
                unwrapInterceptAction(actionOrValue) : (successType) ? 
                mapToAction(successType, actionOrValue, false) : actionOrValue;

                if (act) {
                    storeDispatch(act);
                }
            }

            /**
             * 根據actionOrValue的型別來組裝可以被reduxStore.dispatch呼叫的action
             * @param  {any} actionOrValue
             * @return {Object}
             */
            function mapErrorToActionAndDispatch(actionOrValue) {
                //  攔截器型別的直接呼叫觸發__interceptAction
                if (isInterceptAction(actionOrValue)) {
                    const interceptAction = unwrapInterceptAction(actionOrValue);
                    return storeDispatch(interceptAction);
                }

                //  判斷Logic中的processOptions裡有沒有failType
                if (failType) {
                    //  如果有failType, 組裝一個新的redux action並觸發
                    const act = mapToAction(failType, actionOrValue, true);
                    if (act) {
                        return storeDispatch(act);
                    }
                    return;
                }

                //  actionOrValue本身就是一個異常
                if (actionOrValue instanceof Error) {
                    const act =
                        //  actionOrValue本身包含type, 直接呼叫redux.dispatch(actionOrValue)
                        //  否則包裝出一個redux action(type為UNHANDLED_LOGIC_ERROR), 在呼叫redux.dispatch
                        (actionOrValue.type) ? actionOrValue :
                        {
                            type: UNHANDLED_LOGIC_ERROR,
                            payload: actionOrValue,
                            error: true
                        };
                    return storeDispatch(act);
                }

                //  actionOrValue是一個plain object或一個函式(action creator)
                const typeOfValue = typeof actionOrValue;
                if (actionOrValue && (typeOfValue === `object` || typeOfValue === `function`)) {
                    return storeDispatch(actionOrValue);
                }

                //  非異常/函式/plain object的情況
                storeDispatch({
                    type: UNHANDLED_LOGIC_ERROR,
                    payload: actionOrValue,
                    error: true
                });
            }

            /**
             * 組裝出一個有效的redux action並返回
             * @param  {String|Function} type       redux action type
             * @param  {Object} payload             redux payload
             * @param  {Error|Unfdeined} err        error
             * @return {Object}
             */
            function mapToAction(type, payload, err) {
                //  action type本身是一個action creator, 直接執行type
                if (typeof type === `function`) {
                    return type(payload);
                }
                //  包裝出一個有效的redux action
                const act = { type, payload };
                if (err) { act.error = true; }
                return act;
            }

            // allowMore is now deprecated in favor of variable process arity
            // which sets processOptions.dispatchMultiple = true then
            // expects done() cb to be called to end
            // Might still be needed for internal use so keeping it for now
            const DispatchDefaults = {
                allowMore: false
            };

            /**
             * 觸發
             * @param  {[type]} act     [description]
             * @param  {[type]} options [description]
             * @return {[type]}         [description]
             */
            function dispatch(act, options = DispatchDefaults) {
                const { allowMore } = applyDispatchDefaults(options);
                //  action !== undefined
                if (typeof act !== `undefined`) {
                    /**
                        let action;
                        if (isObservable(act)) {
                            action = act;
                        } else if (isPromise(act)) {
                            action = Observable.fromPromise(act);
                        } else if (act instanceof Error) {
                            action = Observable.throw(act);
                        } else {
                            action = Observable.of(act);
                        }
                        dispatch$.next(action);

                        https://cn.rx.js.org/class/es6/MiscJSDoc.js~ObserverDoc.html#instance-method-next
                     **/
                    dispatch$.next(
                        (isObservable(act)) ? act :
                        (isPromise(act)) ? Observable.fromPromise(act) :
                        (act instanceof Error) ? Observable.throw(act) :
                        Observable.of(act)
                    );
                }
                if (!(dispatchMultiple || allowMore)) {
                   dispatch$.complete();
                }
                return act;
            }

            function applyDispatchDefaults(options) {
                return {
                    ...DispatchDefaults,
                    ...options
                };
            }

            //  拼裝createLogic中相關鉤子函式(validate/tranform/process)中的第一個引數
            const depObj = {
                ...deps,
                cancelled$,
                ctx: {}, // 在不同鉤子中共享資料
                getState,
                action
            };

            function shouldDispatch(act, useDispatch) {
                //  新的action為空
                if (!act) { return false; }
                //  在觸發另外一個action之前, 確保觸發的是一個新的action
                if (useDispatch === `auto`) {
                    return (act.type !== action.type);
                }
                //  否則根據useDispatch是否為空, 返回
                return (useDispatch);
            }

            const AllowRejectNextDefaults = {
                useDispatch: `auto`
            };

            function applyAllowRejectNextDefaults(options) {
                return {
                    ...AllowRejectNextDefaults,
                    ...options
                };
            }

            //  攔截器(validate/tranform)裡的allow或next
            function allow(act, options = AllowRejectNextDefaults) {
                handleNextOrDispatch(true, act, options);
            }
            function reject(act, options = AllowRejectNextDefaults) {
                handleNextOrDispatch(false, act, options);
            }

            //  完成本次action, 在createLogic中的process最後呼叫
            function done() {
                dispatch$.complete();
            }

            /**
             * 對當前攔截器型別action(validate/transform)做一次包裝, 方便後面判斷
             * @param  {Object} act 當前action
             * @return {Object}
             */
            function wrapActionForIntercept(act) {
                if (!act) { return act; }
                return {
                    __interceptAction: act
                };
            }

            /**
             * 判斷傳入的action是否為攔截器型別的
             * @param  {Object}  act 當前action
             * @return {Boolean}
             */
            function isInterceptAction(act) {
                return act && act.__interceptAction;
            }

            /**
             * 對攔截器執行解包
             * @param  {Object}  act 當前action
             * @return {Object}      redux action
             */
            function unwrapInterceptAction(act) {
                return act.__interceptAction;
            }

            /**
             * 攔截器(validate/tranform)裡的allow、reject實現, 觸發新的redux action
             * @param  {Boolean} shouldProcess 是否執行process
             * @param  {Object} act            新的redux action
             * @param  {Object} options
             */
            function handleNextOrDispatch(shouldProcess, act, options) {
                const { useDispatch } = applyAllowRejectNextDefaults(options);
                //  判斷是否應該觸發傳入的redux action
                if (shouldDispatch(act, useDispatch)) {
                    monitor$.next({ action, dispAction: act, name, shouldProcess, op: `nextDisp` });
                    interceptComplete = true;
                    dispatch(wrapActionForIntercept(act), { allowMore: true }); // will be completed later
                    logicActionObs.complete(); // dispatched action, so no next(act)
                } else { // normal next
                    if (act) {
                        monitor$.next({ action, nextAction: act, name, shouldProcess, op: `next` });
                    } else {
                        //  無效的action, 直接結束本次攔截器
                        monitor$.next({ action, name, shouldProcess, op: `filtered` });
                        interceptComplete = true;
                    }
                    postIfDefinedOrComplete(act, logicActionObs);
                }

                //  執行Logic中的process回撥
                if (shouldProcess) {
                    //  組織depObj的action引數
                    depObj.action = act || action;
                    try {
                        const retValue = processFn(depObj, dispatch, done);
                        /**
                            如果在createLogic指定了processOption.dispatchReturn為true, 並且prcess執行完之後返回有效的值
                            就再把返回值作為一個新的redux action進行觸發
                            否則直接結束dispatch$這個Rx.Subject
                            
                            執行process, 並且接收返回值
                            判斷processOption.dispatchReturn
                                成立: 判斷返回值是否有效

                                    有效: dispatch -> mapToActionAndDispatch
                                        mapToActionAndDispatch返回一個有效的action:
                                            繼續reduxStore.dispatch(mapToActionAndDispatch(retValue))

                                    無效: dispatch$.complete -> monitor$.next({ action, name, op: `end` }); cancelled$.complete(); cancelled$.unsubscribe();
                        **/
                        if (dispatchReturn) {
                            if (typeof retValue === `undefined`) {
                                dispatch$.complete();
                            } else {
                                dispatch(retValue);
                            }
                        }
                    } catch (err) {
                        console.error(`unhandled exception in logic named: ${name}`, err);
                        //  執行process的過程中發生異常
                        dispatch(Observable.throw(err));
                    }
                } else {
                    //  傳入的act是一個空值, 或者和當前的type相同, 或者useDispatch不成立
                    dispatch$.complete();
                }
            }

            /**
             * 在本次攔截器之後執行
             * @param  {Object} act       新的action
             * @param  {Rx.Subject} act$  當前action對應的Observable物件
             */
            function postIfDefinedOrComplete(act, act$) {
                //  如果新的action存在, 執行新的action
                if (act) {
                    act$.next(act);
                }
                interceptComplete = true;
                act$.complete();
            }

            //  開始本次action的執行
            function start() {
                intercept(depObj, allow, reject);
            }

            start();
        })
        .takeUntil(cancel$)
        //  規定logicAction$值發出一個值就完成
        .take(1);

    return logicAction$;
}

複製程式碼

以上就是本人對redux-logic的閱讀, 經過閱讀, 大致瞭解到整個redux-logic的執行順序應該是下圖所示的樣子。

redux-logic原始碼閱讀

所有關於原始碼的標註已經放到Github

相關文章