dva源码解析(一)
轉載 :原文 https://blog.csdn.net/zp1996323/article/details/73315096
寫在前面
dva是螞蟻金服推出的一個單頁應用框架,對redux,react-router,redux-saga進行了上層封裝,沒有引入新的概念,但是極大的程度上提升了開發(fā)效率;下面內(nèi)容為本人理解,如有錯誤,還請指出,不勝感激。
redux的痛苦
redux的優(yōu)點很多,痛點也有,比如異步控制,redux-saga的出現(xiàn)使得異步操作變得優(yōu)雅,但是基于redux-saga不得不承認的一點就是開發(fā)過程實在是太麻煩了,假若增加一個操作,不得不操作actions,reducers,sagas,對于sagas可以還需要進行watch,而后還要進行fork;(PS: 本來就夠麻煩了,再加上一個sagas);在添加一個操作時,不得不操作這么多的文件,實在是麻煩,而dva的出現(xiàn)在一定程度上解決了這個問題。
dva基本概念
未使用dva下的目錄經(jīng)常是這樣的:
actions--/ user.js--/ team.js reducers--/ user.js --/ team.js sagas/ --/ user.js --/ team.jsdva將其合并:
models--/ user.js--/ team.js?
dva中有著幾個概念:
namespace => combineReducers中對應的key值 state => 對應初始的state,也就是initialState effects => saga的處理函數(shù) reducers => 對應reducers,不同的是,寫法上將switch...case轉化為對象除了這些以外,dva中還有subscriptions,這一概念來源于elm,
dva的實現(xiàn)
初始化
const app = dva({history: browserHistory });上面的過程發(fā)生了什么?
dva本質(zhì)上調(diào)用了下面函數(shù):
hooks為傳入的一些配置,例如可以通過傳入history來改變路由的實現(xiàn),dva默認采用的是hashHistory;從這里可以看出dva暴露出來的方法:
- app.router():指定路由,需要傳入一個函數(shù),一般類似于({ history }) => (<Router>...</Router>)
- app.use():添加插件,這個稍后來看~
- app.model():添加model,也就是對應的添加一個store下的數(shù)據(jù),該方法做的就是對傳入的model進行檢查,對reducers添加命名空間,而后將其push到_models中。
- namespace必須且唯一,因為內(nèi)置了react-redux-router,所以namespace也不能為routing
- subscriptions與effects均為可選參數(shù),傳入的話必須為對象
- reducers為可選,支持對象和數(shù)組兩種傳入方式(傳入數(shù)組的方式,往往伴隨著高階reducer的應用,具體稍后再看~)
- app.start():初始化應用,接受參數(shù)為選擇器或者DOM節(jié)點
需要注意的是:
- reducers和effects的key不需要用namespace/action的形式了,因為dva會自動將其加上,dispatch的時候,saga需要加上namespace,而saga中的put不需要加入namespace,原因是dva對put進行了重載
- dva同時支持rn應用,引入dva/mobile即可,這時react-router不在需要,利用rn中的Navigator即可,不會引用react-router與react-redux-router,namespace可以命名為routing;正是由于這點差異,作者將路由相關的內(nèi)容作為參數(shù)傳入了進去,具體可以參見[這個文件][1]。
創(chuàng)建
將一些配置項初始化好后,就可以app.start就是來創(chuàng)建一個應用,下面就一點點的看看start的過程(以下基于默認情況,也就是使用了react-router):
- 參數(shù)校驗,是否為DOM元素或者檢查是否可以根據(jù)傳入的選擇器字符串找到對應的DOM,這個DOM對應的就是ReactDOM.render的第二個參數(shù)。
- 錯誤處理,使得發(fā)生錯誤時,不至于應用奔潰,當然需要傳入自定義hooks.onError來處理:
- 遍歷_models,初始化reducers,sagas
處理reducers
對于redux的reducers最常見的是基于switch..case的,而dva做出了一些改變,將每一個case分支變作了一個函數(shù):
(PS: 本人認為,這個可以塊可以更改,利用some操作來盡可能少的調(diào)用無意義的reducer,于是我提了一個pr)
每一個reducer的實現(xiàn)如下:
// actionType對應的是dva的reducers中的key值 (state, action) => {const { type } = action;if (type && actionType !== type) { return state; } return reducer(state, action); };處理sagas
看完了對于reducers的處理,下面來看一下對于sagas的處理:
function getSaga(effects, model, onError) {return function *() { for (const key in effects) { if (Object.prototype.hasOwnProperty.call(effects, key)) { const watcher = getWatcher(key, effects[key], model, onError); const task = yield sagaEffects.fork(watcher); // 為了移除時可以將saga任務注銷 yield sagaEffects.fork(function *() { yield sagaEffects.take(`${model.namespace}/@@CANCEL_EFFECTS`); yield sagaEffects.cancel(task); }); } } }; }getWatcher返回一個saga監(jiān)聽函數(shù),也就是通常寫的watchXXX,model.effects[key]可以是一個任務函數(shù);也可以是個數(shù)組,第一個參數(shù)為任務函數(shù),第二為配置對象,可以傳入type,type有4個可選值,takeEvery(默認),takeLatest,throttle,watcher四種,dva對effects做了一個錯誤處理:
effect => function *(...args) {try {yield effect(...args.concat(createEffects(model))); } catch (e) { onError(e); // 為之前的onErrorWrapper }}注意:
- watcher是指傳入的任務函數(shù)就是一個watcher直接fork就好
- throttle還要傳入一個ms配置,這個ms代表著在多少毫秒內(nèi)只觸發(fā)一次同一類型saga任務,而takeEvery是不會限制同一類型執(zhí)行次數(shù),takeLatest只能執(zhí)行一個同一類型任務,有執(zhí)行中的再次執(zhí)行就會取消
- 由getSaga可以看出,${namespace}/@@CANCEL_EFFECTS可以取消對應的任務監(jiān)聽
- 可以通過配置hooks.onEffect來增加saga的watcher
增強redux
- redux中間件,由sagaMiddware,routerMiddware(啟用react-router時),hooks.onAction傳入的其它中間件,如redux-logger等
- 其它增強,如redux-devtools,內(nèi)置了redux-devtools,另需的話在hooks.extraReducers傳入
設置redux的回調(diào)函數(shù)
通過配置hooks.onStateChange可以指定redux的state改變后所觸發(fā)的回調(diào)函數(shù):
const listeners = plugin.get('onStateChange');for (const listener of listeners) { store.subscribe(() => { listener(store.getState()); }); } }新概念subscriptions
subscriptions是一個新概念,會在dom ready之后執(zhí)行,在這里面可以做一些基礎數(shù)據(jù)的獲取:
一般會將初始數(shù)據(jù)的獲取放在react的生命周期中,比如componentWillMount,但是假設我們做了代碼分割,實現(xiàn)了按需加載,那么我們開始獲取數(shù)據(jù)的時間為:獲取相應的js+解析js+執(zhí)行react生命周期,但是redux的數(shù)據(jù)加載和ui組件沒有太大關系,可以將數(shù)據(jù)獲取的時間點提前,subscriptions提供了解決方法,其意義為訂閱,對于上面的場景,我們可以訂閱路由,到了執(zhí)行的路由執(zhí)行相應的dispatch(),如:
?
(PS: 對于這個新概念,我也不是很清楚,后面的文章會有專門的描述,大家先有一個概念就好)
掛載
上述過程均為初始化的過程,就是獲取到需要的reducers,sagas以及對于一些中間件和插件的配置,下面要進行的就是掛載了,也就熟悉的render(<Provider>, container)。
動態(tài)處理model
dva.model與dva.unmodel,封裝了在運行時的store進行一類增加和刪除的操作,例如可以再切換到某一路由時動態(tài)的加入一個model(個人猜測,熱更新很有可能也利用了這個兩個api與hooks.onHmr)。
未完結
關于redux還有一個利器,那就是高階reduce,當然在dva中也有體現(xiàn),這篇文章已經(jīng)很長了,這些內(nèi)容留在下一篇中介紹。以上是本人對于dva的粗略的理解,內(nèi)容如有錯誤,還請大家指出。dva的確簡化了開發(fā)的流程,而且在螞蟻金服的很多業(yè)務線也有著應用,是一個很值得大家一試!
轉載于:https://www.cnblogs.com/Chasel-Chen/p/9550188.html
新人創(chuàng)作打卡挑戰(zhàn)賽發(fā)博客就能抽獎!定制產(chǎn)品紅包拿不停!總結
以上是生活随笔為你收集整理的dva源码解析(一)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 为什么 c = tf.matmul(a,
- 下一篇: CSS进阶(十)position:rel