一 dva的源码结构
dva的源码分为dva,和dva-core两个部分,dva/src下的文件负责处理对外的逻辑,包括数据校验,app对象封装,配置用户传入的路由等参数,并在最后启动了一个react应用。
dva-core的部分是dva核心功能实现部分,通过create方法返回一个dva对象给dva/src。使用内部的start方法初始化各种被dva/src配置过的属性。
二 dva/src/index.js
1. export的函数的功能
export的函数返回了一个app对象,这个对象挂载了dva所有的属性和方法,其中start方法是应用启动方法。
2. 初始化配置
在导出函数开始,根据用户传入的配置初始化默认的dva配置,包括默认的HashHistory
,react-router-redux提供的routerReducer
,routerMiddleware
(在dva-core里面整合reducer的时候有用到),定义setupApp方法,用于代理history属性到app上。
// history库提供,将HTML5 BOM提供的History API进行封装
const history = opts.history || createHashHistory();
const createOpts = {
initialReducer: {
routing,
},
setupMiddlewares(middlewares) {
return [
routerMiddleware(history),
...middlewares,
];
},
// 在dva-core/index: start方法中调用
// patchHistroy(history)可以监听history变动,从而触发回调
setupApp(app) {
app._history = patchHistory(history);
},
};
function patchHistory(history) {
const oldListen = history.listen;
history.listen = (callback) => {
callback(history.location);
return oldListen.call(history, callback);
};
return history;
}
3. start方法
(1)数据校验
start方法在内部校验了数据,主要有以下几个:
- 1)检查传入的container参数,接受dom元素或者是字符串,用
querySelector
方法查找dom元素,并判断container是否是HTMLElement
元素,依据是否存在nodeName
和nodeType
属性。 - 2)检查是否在之前注册过router,并且要求router是一个函数
(2)调用dva-core的start方法
之后,dva/src下面的start方法调用了dva/core返回的对象的start方法,并且由于要提供用户配置的参数,将dva/src的app对象的this通过call方法传入。
在这个start方法中,dva初始化了store,reducer,model等等,将这些对象全部挂载到了dva/src传入的this下的app对象,也就是最终dva返回的对象。
(3)配置Provider
之所以我们使用dva,可以使用Provider
,connect
方法,而不需要在根组件上做配置,是因为,在这一步,dva已经集成了react-redux插件的provider组件。
// app是dva对象,history默认是用history库的createHashHistory生成的,
// 将BOM的History封装后的history对象。
const DvaRoot = extraProps => (
<Provider store={
store}>
{
router({
app, history: app._history, ...extraProps }) }
</Provider>
);
(4)渲染react组件
用Provider
组件包裹后的JSX对象比传递给了react,用于渲染。
三 dva-core/index
1. create方法
create方法 是被默认导出的。create方法内部创造了一个app对象并挂载了_models,包含dvaModel和所有的用户model,_store默认为null,plugin属性挂载各种插件,这些插件是基于dva生命周期函数的,plugin.use方法被plugin对象代理,以免找错this,model方法用于注册model,start方法用于启动程序。最终create方法返回了这个app对象。
const app = {
_models: [prefixNamespace({
...dvaModel })],
_store: null,
_plugin: plugin,
use: plugin.use.bind(plugin),
model,
start,
};
2. plugin对象
在create方法中,首先创建了Plugin对象,并用filterHooks方法过滤掉不合法的插件。plugin对象负责管理和使用中间件。
// hooks是所有生命周期钩子函数的键名
const hooks = [];
// filterHooks函数过滤了所有的生命周期钩子
export function filterHooks(obj) {
}
export default class Plugin {
constructor() {
// 将this.hooks初始化为一个对象,
}
// 中间件,即是使用plugin.use()方法注册
use(plugin) {
}
// 全局错误处理函数
apply(key, defaultHandler) {
}
// 获取生命周期处理函数
get(key) {
}
}
// 将某一个key对应的生命周期函数的回调函数数组组合起来,生成一个对象
function getExtraReducers(hook) {
}
// 将用户定义的reducer经过每一个reducer中间件处理
function getOnReducer(hook) {
}
在plugin对象中,一共做了这么几件事:
- (1)定义一个hooks生命周期钩子函数数组,并按照这个数组,初始化一个hooks对象,每一个属性是一个生命周期函数,被初始化为一个数组,挂载用户注册的插件。实际上dva的中间件或者说插件就是在这里被使用。
const hooks = [
'onError', 'onStateChange', 'onAction', 'onHmr', 'onReducer', 'onEffect',
'extraReducers', 'extraEnhancers', '_handleActions',
];
- (2)在Plugin的构造函数中,使用reduce方法创建hooks对象
// 将this.hooks初始化为一个对象,
// 它包括上面hooks数组的所有元素作为属性名,每个值都是一个空数组
// 这个空数组用来存放用户注册的中间件
this.hooks = hooks.reduce((memo, key) => {
memo[key] = [];
return memo;
}, {
});
- (3)plugin.use()方法用来注册中间件,注册的过程只是将用户定义的函数push进对应的钩子的数组中。
use(plugin) {
// 数据校验,要求plugin是一个纯对象
const hooks =