摘要:動態(tài)處理與,封裝了在運行時的進行一類增加和刪除的操作,例如可以再切換到某一路由時動態(tài)的加入一個個人猜測,熱更新很有可能也利用了這個兩個與。以上是本人對于的粗略的理解,內(nèi)容如有錯誤,還請大家指出。
寫在前面
dva是螞蟻金服推出的一個單頁應(yīng)用框架,對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.js
dva將其合并:
models --/ user.js --/ team.js
dva中有著幾個概念:
namespace => combineReducers中對應(yīng)的key值 state => 對應(yīng)初始的state,也就是initialState effects => saga的處理函數(shù) reducers => 對應(yīng)reducers,不同的是,寫法上將switch...case轉(zhuǎn)化為對象
除了這些以外,dva中還有subscriptions,這一概念來源于elm,
dva的實現(xiàn) 初始化const app = dva({ history: browserHistory });
上面的過程發(fā)生了什么?
dva本質(zhì)上調(diào)用了下面函數(shù):
function dva(hooks = {}) { const history = hooks.history || defaultHistory; const initialState = hooks.initialState || {}; delete hooks.history; delete hooks.initialState; const plugin = new Plugin(); plugin.use(hooks); const app = { // properties _models: [], _router: null, _store: null, _history: null, _plugin: plugin, _getProvider: null, // methods use, model, router, start, }; return app; }
hooks為傳入的一些配置,例如可以通過傳入history來改變路由的實現(xiàn),dva默認采用的是hashHistory;從這里可以看出dva暴露出來的方法:
app.router():指定路由,需要傳入一個函數(shù),一般類似于({ history }) => (
app.use():添加插件,這個稍后來看~
app.model():添加model,也就是對應(yīng)的添加一個store下的數(shù)據(jù),該方法做的就是對傳入的model進行檢查,對reducers添加命名空間,而后將其push到_models中。
namespace必須且唯一,因為內(nèi)置了react-redux-router,所以namespace也不能為routing
subscriptions與effects均為可選參數(shù),傳入的話必須為對象
reducers為可選,支持對象和數(shù)組兩種傳入方式(傳入數(shù)組的方式,往往伴隨著高階reducer的應(yīng)用,具體稍后再看~)
app.start():初始化應(yīng)用,接受參數(shù)為選擇器或者DOM節(jié)點
需要注意的是:
reducers和effects的key不需要用namespace/action的形式了,因為dva會自動將其加上,dispatch的時候,saga需要加上namespace,而saga中的put不需要加入namespace,原因是dva對put進行了重載
dva同時支持rn應(yīng)用,引入dva/mobile即可,這時react-router不在需要,利用rn中的Navigator即可,不會引用react-router與react-redux-router,namespace可以命名為routing;正是由于這點差異,作者將路由相關(guān)的內(nèi)容作為參數(shù)傳入了進去,具體可以參見這個文件。
創(chuàng)建將一些配置項初始化好后,就可以app.start就是來創(chuàng)建一個應(yīng)用,下面就一點點的看看start的過程(以下基于默認情況,也就是使用了react-router):
參數(shù)校驗,是否為DOM元素或者檢查是否可以根據(jù)傳入的選擇器字符串找到對應(yīng)的DOM,這個DOM對應(yīng)的就是ReactDOM.render的第二個參數(shù)。
錯誤處理,使得發(fā)生錯誤時,不至于應(yīng)用奔潰,當然需要傳入自定義hooks.onError來處理:
// 傳入hooks.onError則調(diào)用,反之調(diào)用默認函數(shù)處理,拋出異常即可 const onError = plugin.apply("onError", (err) => { throw new Error(err.stack || err); }); // 目的是出現(xiàn)錯誤時,也可以進行dispatch操作 const onErrorWrapper = (err) => { if (err) { if (typeof err === "string") err = new Error(err); onError(err, app._store.dispatch); } };
遍歷_models,初始化reducers,sagas
const sagas = []; // initalReducer為{ routing: routerReducer } const reducers = { ...initialReducer }; // 為rootReducer for (const m of this._models) { // 得到默認的state reducers[m.namespace] = getReducer(m.reducers, m.state); if (m.effects) sagas.push(getSaga(m.effects, m, onErrorWrapper)); }處理reducers
對于redux的reducers最常見的是基于switch..case的,而dva做出了一些改變,將每一個case分支變作了一個函數(shù):
(PS: 本人認為,這個可以塊可以更改,利用some操作來盡可能少的調(diào)用無意義的reducer,于是我提了一個pr)
每一個reducer的實現(xiàn)如下:
// actionType對應(yīng)的是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任務(wù)注銷 yield sagaEffects.fork(function *() { yield sagaEffects.take(`${model.namespace}/@@CANCEL_EFFECTS`); yield sagaEffects.cancel(task); }); } } }; }
getWatcher返回一個saga監(jiān)聽函數(shù),也就是通常寫的watchXXX,model.effects[key]可以是一個任務(wù)函數(shù);也可以是個數(shù)組,第一個參數(shù)為任務(wù)函數(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是指傳入的任務(wù)函數(shù)就是一個watcher直接fork就好
throttle還要傳入一個ms配置,這個ms代表著在多少毫秒內(nèi)只觸發(fā)一次同一類型saga任務(wù),而takeEvery是不會限制同一類型執(zhí)行次數(shù),takeLatest只能執(zhí)行一個同一類型任務(wù),有執(zhí)行中的再次執(zhí)行就會取消
由getSaga可以看出,${namespace}/@@CANCEL_EFFECTS可以取消對應(yīng)的任務(wù)監(jiān)聽
可以通過配置hooks.onEffect來增加saga的watcher
增強reduxredux中間件,由sagaMiddware,routerMiddware(啟用react-router時),hooks.onAction傳入的其它中間件,如redux-logger等
其它增強,如redux-devtools,內(nèi)置了redux-devtools,另需的話在hooks.extraReducers傳入
const enhancers = [ applyMiddleware(...middlewares), devtools(), ...extraEnhancers, ]; const store = this._store = createStore( // eslint-disable-line createReducer(), initialState, compose(...enhancers), );設(shè)置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í)行,在這里面可以做一些基礎(chǔ)數(shù)據(jù)的獲取:
一般會將初始數(shù)據(jù)的獲取放在react的生命周期中,比如componentWillMount,但是假設(shè)我們做了代碼分割,實現(xiàn)了按需加載,那么我們開始獲取數(shù)據(jù)的時間為:獲取相應(yīng)的js+解析js+執(zhí)行react生命周期,但是redux的數(shù)據(jù)加載和ui組件沒有太大關(guān)系,可以將數(shù)據(jù)獲取的時間點提前,subscriptions提供了解決方法,其意義為訂閱,對于上面的場景,我們可以訂閱路由,到了執(zhí)行的路由執(zhí)行相應(yīng)的dispatch(),如:
setup({ dispatch, history }) { return history.listen(({ pathname, query }) => { if (pathname === "/users") { dispatch({ type: "fetch", payload: query }); } }); }
(PS: 對于這個新概念,我也不是很清楚,后面的文章會有專門的描述,大家先有一個概念就好)
掛載上述過程均為初始化的過程,就是獲取到需要的reducers,sagas以及對于一些中間件和插件的配置,下面要進行的就是掛載了,也就熟悉的render(
dva.model與dva.unmodel,封裝了在運行時的store進行一類增加和刪除的操作,例如可以再切換到某一路由時動態(tài)的加入一個model(個人猜測,熱更新很有可能也利用了這個兩個api與hooks.onHmr)。
未完結(jié)關(guān)于redux還有一個利器,那就是高階reduce,當然在dva中也有體現(xiàn),這篇文章已經(jīng)很長了,這些內(nèi)容留在下一篇中介紹。以上是本人對于dva的粗略的理解,內(nèi)容如有錯誤,還請大家指出。dva的確簡化了開發(fā)的流程,而且在螞蟻金服的很多業(yè)務(wù)線也有著應(yīng)用,是一個很值得大家一試!
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/83474.html
摘要:下面會從淺到深,淡淡在閱讀源碼過程中自己的理解。分拆子頁面后,每一個子頁面對應(yīng)一個文件??偨Y(jié)上面就是最早版本的源碼,很簡潔的使用了等其目的也很簡單簡化相關(guān)生態(tài)的繁瑣邏輯參考源碼地址 ??dva的思想還是很不錯的,大大提升了開發(fā)效率,dva集成了Redux以及Redux的中間件Redux-saga,以及React-router等等。得益于Redux的狀態(tài)管理,以及Redux-saga中...
摘要:下面會從淺到深,淡淡在閱讀源碼過程中自己的理解。分拆子頁面后,每一個子頁面對應(yīng)一個文件。總結(jié)上面就是最早版本的源碼,很簡潔的使用了等其目的也很簡單簡化相關(guān)生態(tài)的繁瑣邏輯參考源碼地址 ??dva的思想還是很不錯的,大大提升了開發(fā)效率,dva集成了Redux以及Redux的中間件Redux-saga,以及React-router等等。得益于Redux的狀態(tài)管理,以及Redux-saga中...
摘要:下面會從淺到深,淡淡在閱讀源碼過程中自己的理解。分拆子頁面后,每一個子頁面對應(yīng)一個文件??偨Y(jié)上面就是最早版本的源碼,很簡潔的使用了等其目的也很簡單簡化相關(guān)生態(tài)的繁瑣邏輯參考源碼地址 ??dva的思想還是很不錯的,大大提升了開發(fā)效率,dva集成了Redux以及Redux的中間件Redux-saga,以及React-router等等。得益于Redux的狀態(tài)管理,以及Redux-saga中...
摘要:多端統(tǒng)一開發(fā)框架優(yōu)秀學習資源匯總官方資源項目倉庫官方文檔項目倉庫官方文檔微信小程序官方文檔百度智能小程序官方文檔支付寶小程序官方文檔字節(jié)跳動小程序官方文檔文章教程不敢閱讀包源碼帶你揭秘背后的哲學從到構(gòu)建適配不同端微信小程序等的應(yīng)用小程序最 Awesome Taro 多端統(tǒng)一開發(fā)框架 Taro 優(yōu)秀學習資源匯總 showImg(https://segmentfault.com/img/r...
閱讀 2900·2019-08-30 15:55
閱讀 2009·2019-08-30 14:02
閱讀 1248·2019-08-29 15:23
閱讀 1014·2019-08-29 11:27
閱讀 468·2019-08-26 11:43
閱讀 3196·2019-08-26 10:32
閱讀 1261·2019-08-23 14:41
閱讀 3304·2019-08-23 14:41