dva: 基于redux、react-redux、redux-saga、react-router 的轻量级框架
dva 只是对上面组件的一层封装,react该怎么写还是怎么写,不影响组件的写法
查看npm的dva包
├── /dist/ # 项目输出目录
├── /lib/ # 项目源码编译后的库目录
├── /src/ # 项目源码目录
│ ├── createDva.js # 主要源码目录
│ ├── handleActions.js
│ ├── index.js # 源码入口文件
│ ├── mobile.js # 移动端入口文件及源码
│ └── plugin # 插件加载工具函数
├── index.js # dva默认入口
├── mobile.js # dva 移动端支持入口
└── router.js # 路由输出
进入src目录进行源码查看,index.js是作为项目的入口文件,执行createDva,导出dva函数
import hashHistory from 'react-router/lib/hashHistory';
import {
routerMiddleware,
syncHistoryWithStore,
routerReducer as routing,
} from 'react-router-redux';
import createDva from './createDva';
export default createDva({
mobile: false,
initialReducer: {
routing,
},
defaultHistory: hashHistory,
routerMiddleware,
setupHistory(history) {
this._history = syncHistoryWithStore(history, this._store);
},
});
进入 './createDva',我们先看看它 import 了哪些主要工具: react-redux、redux、redux-saga。
redux-saga 是 redux 的一个中间件,用于 effects 管理。
export出createDva,在 src/index 里面已经执行了,它返回的是一个函数dva,dva执行则返回一个app对象,也就是我们使用dva的那几个简单api操作接口对象,
return function dva(hooks = {}) {
// history and initialState does not pass to plugin
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,
// methods
use,
model,
router,
start,
};
return app;
我们使用dva进行如下操作
import { message } from 'antd'
import dva from 'dva'
import createLoading from 'dva-loading'
import { browserHistory } from 'dva/router'
import 'babel-polyfill'
// 1. Initialize
const app = dva({
...createLoading({
effects: true,
}),
history: browserHistory,
onError (error) {
message.error(error.message)
},
})
// 2. Model
app.model(require('./models/app'))
// 3. Router
app.router(require('./router'))
// 4. Start
app.start('#root')
接下来我们仔细分析app对象里面的这几个属性。
_models为一个数组,保存的是当前已经加载的model,就是我们写的包含的namespace、subscriptions、effects、reducers 的models目录文件。用于提取store信息,方便redux-sage和redux处理。
_router为一个数组,保存的是我们定义的路由信息,用于直接添加到react-redux的Provider里面,没有其他处理。所以说dva对于路由并没有特殊处理,用的就是 react-router。
const store = this._store = createStore(
createReducer(),
initialState,
compose(...enhancers),
);
存储当前namespace下的store信息,createStore为redux的构建store方法
用于对history的一个重新赋值,真实起效的是history
保存当前使用的plugin信息,
const plugin = new Plugin();
plugin.use(hooks);
dva api use
加载插件
dva api model
加载model,将其push到_model数组中
dva api router
router,将其push到_router数组中
前面一些私有属性和3个公共方法,都是为start方法做准备的
const sagas = [];
const reducers = { ...initialReducer };
for (const m of this._models) {
reducers[m.namespace] = getReducer(m.reducers, m.state);
if (m.effects) sagas.push(getSaga(m.effects, m, onErrorWrapper));
}
reducer加载_model的reducer,saga加载_model的effects
1.首先创建 sagaMiddleware ,使用 saga 提供的 createSagaMiddleware() 方法创建。
const sagaMiddleware = createSagaMiddleware();
2.然后构建 middlewares 数组,由 sagaMiddleware 和其他的 extraMiddlewares 构成,用于使用 redux 的 applyMiddleware 来加载 redux 所需的中间件。
let middlewares = [sagaMiddleware,...flatten(extraMiddlewares),];
3.构建 enhancers ,组成一个函数数组,我理解的它主要是effects,用于后面构建 store
const enhancers = [
applyMiddleware(...middlewares),
devtools(),
...extraEnhancers,
];
4.构建 store ,使用 redux 提供的 createStore()方法创建 store,
const store = this._store = createStore(
createReducer(),
initialState,
compose(...enhancers),
);
function createReducer(asyncReducers) {
return reducerEnhancer(combineReducers({
...reducers,
...extraReducers,
...asyncReducers,
}));
}
构建好了 store 之后,如何应用呢?
先需要将 saga 中间件启动起来
sagas.forEach(sagaMiddleware.run);
绑定 history
if (setupHistory) setupHistory.call(this, history);
执行数据源订阅,获取 _model 中的subscriptions,其中,runSubscriptions会执行 redux 的 dispatch 方法,来启动subscriptions
const unlisteners = {};
for (const model of this._models) {
if (model.subscriptions) {
unlisteners[model.namespace] = runSubscriptions(model.subscriptions, model, this,
onErrorWrapper);
}
}
最后,才来应用 store: 这里要使用 react-dom 的 render 方法来渲染dom;使用 react-redux 的 Provider 将 react 与 redux连接起来并共享 store;
if (container) {
render(container, store, this, this._router);
plugin.apply('onHmr')(render.bind(this, container, store, this));
} else {
return getProvider(store, this, this._router);
}
function getProvider(store, app, router) {
return extraProps => (
<Provider store={store}>
{ router({ app, history: app._history, ...extraProps }) }
</Provider>
);
}
function render(container, store, app, router) {
const ReactDOM = require('react-dom');
ReactDOM.render(React.createElement(getProvider(store, app, router)), container);
}
到这里就,基本结束了dva的源码大概解析。
我这里只分析了它实现的基本原理,它的一些实现细节需要你去具体分析,它还包含了plugin、saga方法等的应用。
接下来总结一下它的实现步骤
createDva 函数,执行并返回 dva 方法对象dva 方法执行返回的 app 对象,首先构建基础的 _models, _router, _store, _history, _plugin, use, model, router,这些有的是数组,有的是函数start,也是最重要的一个环节,这里构建了 saga,监听了 subscription,构建了 store,redux、saga、react结合起来,应用到项目中对于一个model的处理,比如injectModel,动态插入model时,分三步
这里的简单总结,也是我自己对于dva应用的学习总结