在用React和Redux做開發時, 都會用到非同步的一些東西, 之前更多的用的是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
進行配置(超時警告時間等), 我們主要看createLogic
和createLogicMiddleware
:
- 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, 裡面呼叫了naming
和wrapper
, 下面我們逐個分析下。
先看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
的執行順序應該是下圖所示的樣子。
所有關於原始碼的標註已經放到Github。