dva的思想仍是很不錯的,大大提高了開發效率,dva集成了Redux以及Redux的中間件Redux-saga,以及React-router等等。得益於Redux的狀態管理,以及Redux-saga中經過Task和Effect來處理異步的概念,dva在這些工具的基礎上高度封裝,只暴露出幾個簡單的API就能夠設計數據模型。react
最近看了一下Redux-saga的源碼,結合以及以前在項目中一直採用的是redux-dark模式來將reducers和sagas(generator函數處理異步)拆分到不一樣的子頁面,每個頁面中同一個文件中包含了該頁面狀態的reducer和saga,這種簡單的封裝已經能夠大大的提高項目的可讀性。git
最近看了dva源碼,熟悉了dva是在上層如何作封裝的。下面會從淺到深,淡淡在閱讀dva源碼過程當中本身的理解。github
- redux-dark模式
- dva 0.0.12版本的使用和源碼理解
本文的原文地址爲: https://github.com/forthealll...
歡迎starredux
在使用redux和redux-saga的時候,特別是如何存放reducer函數和saga的generator函數,這兩個函數是直接跟如何處理數據掛鉤的。api
回顧一下,在redux中使用異步中間件redux-saga後,完整的數據和信息流向:react-router
在存在異步的邏輯下,在UI Component中發出一個plain object的action,而後通過redux-saga這個中間件處理,redux-saga會將這個action傳入相應channel,經過redux-saga的effect方法(好比call、put、apply等方法)生成一個描述對象,而後將這個描述對象轉化成具備反作用的函數並執行。app
在redux-saga執行具備反作用的函數時,又能夠dispatch一個action,這個action也是一個plain object,會直接傳入到redux的reducer函數中進行處理,也就是說在redux-saga的task中發出的action,就是同步的action。異步
簡單的歸納:從UI組件上發出的action通過了2層的處理,分別是redux-saga中間件和redux的reducer函數。async
redux-dark模式很簡單,就是將同一個子頁面下的redux-saga處理action的saga函數,以及reducer處理該子頁面下的state的reducer函數,放在同一個文件中。ide
舉例來講:
import { connect } from 'react-redux'; class Hello extends React.Component{ componentDidMount(){ //發出一個action,獲取異步數據 this.props.dispatch({ type:'async_count' }) } } export default connect({})(Hello);
從Hello組件中發出一個type = 'async_count'的action,咱們用redux-dark模式來將saga和reducer函數放在同一個文件中:
import { takeEvery } from 'redux-saga/effects'; //saga function * asyncCount(){ console.log('執行了saga異步...') //發出一個原始的action yield put({ type:'async' }); } function * helloSaga(){ //接受從UI組件發出的action takeEvery('async_count',asyncCount); } //reducer function helloReducer(state,action){ if(action.type === 'count'); return { ...state,count + 1} }
上述就是一個將saga和reducer放在同一個文件裏面的例子。redux-dark模式來組織代碼,能夠顯得比較直觀,統一了數據的處理層。分拆子頁面後,每個子頁面對應一個文件。可讀性很高。
上述的redux-dark模式,就是一種簡單的處理,而dva的話是作了更近一步的封裝,dva不只封裝了redux和redux-saga,還有react-router-redux、react-router等等。使得咱們能夠經過很簡單的配置,就能使用redux、redux-saga、react-router等。
下面首先以dva的初始版本爲例來理解一下dva的源碼。
來看官網給的使用dva 0.0.12的例子:
// 1. Initialize const app = dva(); // 2. Model app.model({ namespace: 'count', state: 0, effects: { ['count/add']: function*() { console.log('count/add'); yield call(delay, 1000); yield put({ type: 'count/minus', }); }, }, reducers: { ['count/add' ](count) { return count + 1 }, ['count/minus'](count) { return count - 1 }, }, subscriptions: [ function(dispatch) { //..處理監聽等等函數 } ], }); // 3. View const App = connect(({ count }) => ({ count }))(function(props) { return ( <div> <h2>{ props.count }</h2> <button key="add" onClick={() => { props.dispatch({type: 'count/add'})}}>+</button> <button key="minus" onClick={() => { props.dispatch({type: 'count/minus'})}}>-</button> </div> ); }); // 4. Router app.router(({ history }) => <Router history={history}> <Route path="/" component={App} /> </Router> ); // 5. Start app.start(document.getElementById('root'));
只要三步就完成了一個例子,如何處理action呢,咱們以一個圖來表示:
也就是作UI組件上發出的對象類型的action,先去根據類型匹配=model初始化時候,effects屬性中的action type。
在dva初始化過程當中的effects屬性中的函數,其實就是redux-saga中的saga函數,在該函數中處理直接的異步邏輯,而且該函數能夠二次發出同步的action。
此外dva還能夠經過router方法初始化路由等。
下面來直接讀讀dva 0.0.12的源碼,下面的代碼是通過我精簡後的dva的源碼:
//Provider全局注入store import { Provider } from 'react-redux'; //redux相關的api import { createStore, applyMiddleware, compose, combineReducers } from 'redux'; //redux-saga相關的api,takeEvery和takeLatest監聽等等 import createSagaMiddleware, { takeEvery, takeLatest } from 'redux-saga'; //react-router相關的api import { hashHistory, Router } from 'react-router'; //在react-router4.0以後已經較少使用,將路由的狀態存儲在store中 import { routerMiddleware, syncHistoryWithStore, routerReducer as routing } from 'react-router-redux'; //redux-actions的api,能夠以函數式描述reducer等 import { handleActions } from 'redux-actions'; //redux-saga非阻塞調用effect import { fork } from 'redux-saga/effects'; function dva() { let _routes = null; const _models = []; //new dva暴露了3個方法 const app = { model, router, start, }; return app; //添加models,一個model對象包含了effects,reducers,subscriptions監聽器等等 function model(model) { _models.push(model); } //添加路由 function router(routes) { _routes = routes; } function start(container) { let sagas = {}; //routing是react-router-redux的routerReducer別名,用於擴展reducer,這樣之後擴展後的reducer就能夠處理路由變化。 let reducers = { routing }; _models.forEach(model => { //對於每個model,提取其中的reducers和effects,其中reducers用於擴展redux的reducers函數,而effects用於擴展redx-saga的saga處理函數。 reducers[model.namespace] = handleActions(model.reducers || {}, model.state); //擴展saga處理函數,sagas是包含了全部的saga處理函數的對象 sagas = { ...sagas, ...model.effects }; ---------------------------(1) }); reducers = { ...reducers }; //獲取決定使用React-router中的那一個api const _history = opts.history || hashHistory; //初始化redux-saga const sagaMiddleware = createSagaMiddleware(); //爲redux添加中間件,這裏添加了處理路由的中間件,以及redux-saga中間件。 const enhancer = compose( applyMiddleware.apply(null, [ routerMiddleware(_history), sagaMiddleware ]), window.devToolsExtension ? window.devToolsExtension() : f => f ); const initialState = opts.initialState || {}; //經過combineReducers來擴展reducers,同時生成擴展後的store實例 const store = app.store = createStore( combineReducers(reducers), initialState, enhancer ); // 執行model中的監聽函數,監聽函數中傳入store.dispatch _models.forEach(({ subscriptions }) => { if (subscriptions) { subscriptions.forEach(sub => { store.dispatch, onErrorWrapper); }); } }); // 根據rootSaga來啓動saga,rootSaga就是redux-saga運行的主task sagaMiddleware.run(rootSaga); //建立history實例子,能夠監聽store中的state的變化。 let history; history = syncHistoryWithStore(_history, store); --------------------------------(2) // Render and hmr. if (container) { render(); apply('onHmr')(render); } else { const Routes = _routes; return () => ( <Provider store={store}> <Routes history={history} /> </Provider> ); } function getWatcher(k, saga) { let _saga = saga; let _type = 'takeEvery'; if (Array.isArray(saga)) { [ _saga, opts ] = saga; _type = opts.type; } function* sagaWithErrorCatch(...arg) { try { yield _saga(...arg); } catch (e) { onError(e); } } if (_type === 'watcher') { return sagaWithErrorCatch; } else if (_type === 'takeEvery') { return function*() { yield takeEvery(k, sagaWithErrorCatch); }; } else { return function*() { yield takeLatest(k, sagaWithErrorCatch); }; } } function* rootSaga() { for (let k in sagas) { if (sagas.hasOwnProperty(k)) { const watcher = getWatcher(k, sagas[k]); yield fork(watcher); } -----------------------------(3) } } function render(routes) { const Routes = routes || _routes; ReactDOM.render(( <Provider store={store}> <Routes history={history} /> </Provider> ), container); } } } export default dva;
代碼的閱讀在上面都以注視的方式給出,值得注意的主要有一下3點:
const reducer = handleActions( { INCREMENT: (state, action) => ({ counter: state.counter + action.payload }), DECREMENT: (state, action) => ({ counter: state.counter - action.payload }) }, { counter: 0 } );
INCREMENT和DECREMENT屬性的函數就能夠分別處理,type = "INCREMENT"和type = "DECREMENT"的action。
function* rootSaga() { for (let k in sagas) { if (sagas.hasOwnProperty(k)) { const watcher = getWatcher(k, sagas[k]); yield fork(watcher); } } }
從全局的包含全部saga函數的sagas對象中,獲取相應的屬性,並fork相應的監聽,這裏的監聽經常使用的有takeEvery和takeLatest等兩個redux-saga的API等。
總結:上面就是dva最先版本的源碼,很簡潔的使用了redux、redux-saga、react-router、redux-actions、react-router-redux等.其目的也很簡單:
簡化redux相關生態的繁瑣邏輯