Dva 源码解析
https://github.com/dvajs/dva/tree/master/packages
实现 reducers (基本计数器-reducers示例)
dva/index.js
import React from 'react';import ReactDOM from 'react-dom';import {createStore, combineReducers} from 'redux';import {Provider, connect} from 'react-redux';import prefixNamespace from './prefixNamespace'export {connect,}function dva(){const app = {model,_models: [],router,_router: null,start,}let initialReducers = {}; //定义一个初始化的reducers合并对象function model(model){// 给action添加namespace前缀// 如:把 add(state, action){} 改为 [counter1/add](state, action){}const prefixedModel = prefixNamespace(model);app._models.push(prefixedModel);return prefixedModel;}function router(router){app._router = router;}function start(selector){// 把每一个model的reducers添加到initialReducers中app._models.forEach(model => {initialReducers[model.namespace] = getReducer(model);})let rootReducer = createReducer(); //创建根rootReducerlet store = createStore(rootReducer); //创建仓库store// 合并所有model的reducersfunction createReducer(){return combineReducers(initialReducers);}ReactDOM.render((<Provider store={store}>{app._router({})}</Provider>), document.querySelector(selector));}return app;};// 从子model中获取子reducerfunction getReducer(model){const {reducers, state: initialState} = model;function reducer(state=initialState, action){const matchReducer = reducers[action.type];// 如果有匹配,返回新数据;没有匹配到,返回旧数据。if (matchReducer) return matchReducer(state, action);return state;}return reducer;}export default dva;
dva/prefixNamespace.js
function prefix(obj, namespace){return Object.keys(obj).reduce((memo, key) => {const newKey = `${namespace}/${key}`;memo[newKey] = obj[key];return memo;}, {})}/** 给model的action添加namespace前缀** @param {*} model*/function prefixNamespace(model){if (model.reducers){model.reducers = prefix(model.reducers, model.namespace);}return model;}export default prefixNamespace;
实现 effects
dva/index.js
import React from 'react';import ReactDOM from 'react-dom';+ import {createStore, combineReducers, applyMiddleware} from 'redux';import {Provider, connect} from 'react-redux';+ import createSagaMiddleware from 'redux-saga';import prefixNamespace from './prefixNamespace'+ import getSagas from './getSagas'export {connect,}function dva(){const app = {model,_models: [],router,_router: null,start,}const initialReducers = {}; //定义一个初始化的reducers合并对象function model(model){// 给action添加namespace前缀// 如:把 add(state, action){} 改为 [counter1/add](state, action){}const prefixedModel = prefixNamespace(model);app._models.push(prefixedModel);return prefixedModel;}function router(router){app._router = router;}function start(selector){// 把每一个model的reducers添加到initialReducers中app._models.forEach(model => {initialReducers[model.namespace] = getReducer(model);})const rootReducer = createReducer(); //创建根rootReducer+ const sagas = getSagas(app); //获取所有model的saga集合 [model1-Saga, model2-Saga]+ const sagaMiddleware = createSagaMiddleware(); //创建saga中间件+ const store = applyMiddleware(sagaMiddleware)(createStore)(rootReducer); // 创建仓库store+ sagas.forEach(saga => sagaMiddleware.run(saga)); //启动saga// 合并所有model的reducersfunction createReducer(){return combineReducers(initialReducers);}ReactDOM.render((<Provider store={store}>{app._router({})}</Provider>), document.querySelector(selector));}return app;};// 从子model中获取子reducerfunction getReducer(model){const {reducers, state: initialState} = model;function reducer(state=initialState, action){const matchReducer = reducers[action.type];// 如果有匹配,返回新数据;没有匹配到,返回旧数据。if (matchReducer) return matchReducer(state, action);return state;}return reducer;}export default dva;
dva/prefixNamespace.js
function prefix(obj, namespace){return Object.keys(obj).reduce((memo, key) => {const newKey = `${namespace}/${key}`;memo[newKey] = obj[key];return memo;}, {})}/** 给model的action添加namespace前缀** @param {*} model*/function prefixNamespace(model){if (model.reducers){model.reducers = prefix(model.reducers, model.namespace);}+ if (model.effects){+ model.effects = prefix(model.effects, model.namespace);+ }return model;}export default prefixNamespace;
dva/getSagas.js
import * as sagaEffects from 'redux-saga/effects';// 返回所有子model saga 的集合function getSagas(app){let sagas = [];for (const model of app._models){sagas.push(getSaga(model.effects, model));}return sagas;}/** 返回子model的saga** @param {*} effects* @param {*} model*/function getSaga(effects, model){return function* (){for (const key in effects){const watcher = getWatcher(key, effects[key], model);yield sagaEffects.fork(watcher); //fork一个子进程执行 watcherSaga}}}/** 获取 watcherSaga (rootSaga -> watcherSaga -> workerSaga)* 当每次向仓库派发asyncAdd动作的时候,都会执行 asyncAddEffect saga** @param {*} key 监听动作:asyncAdd* @param {*} effect 工作saga:asyncAddEffect,即:function* asyncAdd(action){...}* @param {*} model 子model*/function getWatcher(key, effect, model){return function* (){// 每当key动作派发的时候,saga在执行effect(工作saga)的时候,会向它默认传递actionyield sagaEffects.takeEvery(key, function* sagaWithCatch(...args){yield effect(...args,{...sagaEffects,// 重写put方法,给本model的action.type 加上namespace前缀put: action => sagaEffects.put({...action, type: prefixType(action.type, model)}),},);});}}/** 给 action.type 加上namespace前缀** @param {*} type 动作类型* @param {*} model 子model*/function prefixType(type, model){const {namespace} = model;// 如果派发本model的动作,不加前缀if (type.indexOf('/') === -1){return `${namespace}/${type}`;} else {if (type.split('/')[0] === namespace){console.warn(`Warning: [sagaEffects.put] ${type} should not be prefixed with namespace ${namespace}`);}// 如果派发其它model的动作,需要加前缀return type;}}export default getSagas;
支持路由
dva/router.js
export * from 'react-router-dom';
dva/index.js
...+ import {createHashHistory} from 'history';+function dva(options={}){...+ const history = options.history || createHashHistory();...function start(selector){...ReactDOM.render((<Provider store={store}>+ {app._router({app, history})}</Provider>), document.querySelector(selector));}return app;};...export default dva;
支持路由跳转
dva 源码中用的 react-router-redux,该库现在已废弃,被 connected-react-router 替代了。
dva/router.js
+ import * as routerRedux from 'connected-react-router';export * from 'react-router-dom';+ export {routerRedux};
dva/index.js
import React from 'react';import ReactDOM from 'react-dom';import {createStore, combineReducers, applyMiddleware} from 'redux';import {Provider, connect} from 'react-redux';import createSagaMiddleware from 'redux-saga';+ import {routerMiddleware, connectRouter, ConnectedRouter} from 'connected-react-router';import {createHashHistory} from 'history';import prefixNamespace from './prefixNamespace'import getSagas from './getSagas'export {connect,}function dva(options={}){const app = {model,_models: [],router,_router: null,start,}const history = options.history || createHashHistory();+ const initialReducers = {router: connectRouter(history)}; //定义一个初始化的reducers合并对象function model(model){// 给action添加namespace前缀// 如:把 add(state, action){} 改为 [counter1/add](state, action){}const prefixedModel = prefixNamespace(model);app._models.push(prefixedModel);return prefixedModel;}function router(router){app._router = router;}function start(selector){// 把每一个model的reducers添加到initialReducers中app._models.forEach(model => {initialReducers[model.namespace] = getReducer(model);})const rootReducer = createReducer(); //创建根rootReducerconst sagas = getSagas(app); //获取所有model的saga集合 [model1-Saga, model2-Saga]const sagaMiddleware = createSagaMiddleware(); //创建saga中间件// 创建仓库storeconst store = applyMiddleware(+ routerMiddleware(history),sagaMiddleware)(createStore)(rootReducer);sagas.forEach(saga => sagaMiddleware.run(saga)); //启动saga// 合并所有model的reducersfunction createReducer(){return combineReducers(initialReducers);}ReactDOM.render((<Provider store={store}>{app._router({app, history})}</Provider>), document.querySelector(selector));}return app;};// 从子model中获取子reducerfunction getReducer(model){const {reducers, state: initialState} = model;function reducer(state=initialState, action){const matchReducer = reducers[action.type];// 如果有匹配,返回新数据;没有匹配到,返回旧数据。if (matchReducer) return matchReducer(state, action);return state;}return reducer;}export default dva;
dva/saga.js
export * from 'redux-saga';
dva/dynamic
https://github.com/dvajs/dva/blob/master/packages/dva/src/dynamic.js
dva-loading
https://github.com/dvajs/dva/tree/master/packages/dva-loading
