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相关生态的繁琐逻辑