Dva 框架源码解析
dva 是一个基于 Redux 和 redux-saga 的轻量级状态管理框架,由蚂蚁金服团队开源。它旨在简化 React 应用中的状态管理,提供了一套类似 Vue 和 Angular 中的单文件组件开发模式。本文将从前端架构师的视角,结合源码深入解析 dva 的整体架构、模块划分和调用流程。
整体架构
dva 的整体架构如下图所示:
从图中可以看出,dva 由以下几个核心模块组成:
dva-core
: dva 的核心库,提供了 app.model、app.router 等 API,以及 Redux store 的创建和订阅功能。dva-loading
: 用于处理异步操作的 loading 状态。dva-immer
: 用于实现 immer 方式的不可变数据更新。dva-router
: 对 react-router 的封装,用于处理路由。
其中,dva-core
是 dva 的核心,它提供了 app.model
、app.router
、app.start
等 API,用于定义 model、路由和启动应用。
模块划分
接下来,我们详细分析 dva-core
的主要模块。
app.model
app.model
方法用于定义 model。model 是 dva 中的核心概念,表示一个领域模型,包含 state、reducers、effects 等属性。当调用 app.model
方法时,dva 会将 model 对象保存到 app._models
数组中,供后续使用。
以下是 app.model
的简化实现:
export function model(model) {
const prefixedModel = prefixNamespace({ ...model });
this._models.push(prefixedModel);
return prefixedModel;
}
app.router
app.router
方法用于定义路由。当调用 app.router
方法时,dva 会将传入的函数保存到 app._router
属性中,在 app.start
方法中调用该函数,并将返回的 React 组件渲染到页面中。
以下是 app.router
的简化实现:
export function router(router) {
invariant(isFunction(router), 'app.router: router should be function');
this._router = router;
}
app.start
app.start
方法用于启动应用。当调用 app.start
方法时,dva 会依次执行以下操作:
- 创建 Redux store,并注入
dva-loading
和dva-immer
中间件。 - 遍历
app._models
数组,调用injectModel
方法注入 model。 - 运行
app._router
函数,获取 React 组件并渲染到页面中。 - 订阅 Redux store 的变更,并触发页面重新渲染。
以下是 app.start
的简化实现:
export function start(container) {
// 创建 Redux store
const store = createStore();
this._store = store;
// 注入 model
this._models.forEach(model => injectModel(model, store));
// 运行 router 函数,渲染 React 组件
const RootComponent = this._router({ history: this._history, app: this });
ReactDOM.render(
React.createElement(Provider, { store }, RootComponent),
container
);
// 订阅 store 变更
this._unlisteners.push(store.subscribe(() => {
ReactDOM.render(
React.createElement(Provider, { store }, RootComponent),
container
);
}));
}
injectModel
injectModel
方法用于将 model 注入到 Redux store 中。它的主要步骤如下:
- 根据 model 的定义,创建对应的 reducer 函数。
- 使用
redux-saga
创建 saga 中间件,并将 model 中定义的 effect 函数作为 saga 任务运行。 - 将 reducer 和 saga 中间件注入到 Redux store 中。
以下是 injectModel
的简化实现:
export function injectModel(model, store) {
// 创建 reducer
const reducer = createReducer(model);
const sagas = getSagas(model);
// 创建 saga 中间件
const sagaMiddleware = createSagaMiddleware();
const enhancer = applyMiddleware(sagaMiddleware);
// 注入 reducer 和 saga 中间件
store.replaceReducer(mergeReducers(store.asyncReducers, reducer));
store.runSaga = sagaMiddleware.run;
sagas.forEach(saga => store.runSaga(saga));
}
调用流程
最后,我们来总结一下 dva 的调用流程:
整体调用流程如下:
定义 Model
- 通过调用
app.model
方法定义 model。 - 在
app.model
方法中,将 model 对象保存到app._models
数组中。
定义 Router
- 通过调用
app.router
方法定义路由。 - 在
app.router
方法中,将传入的路由函数保存到app._router
属性中。
启动应用
- 通过调用
app.start
方法启动应用。 - 在
app.start
方法中,执行以下步骤:- 调用
createStore
创建 Redux store。- 创建 Redux store 对象。
- 注入
dva-loading
和dva-immer
中间件到 store 中。
- 遍历
app._models
数组,依次执行以下操作:- 调用
injectModel
方法注入 model。- 根据 model 的定义创建对应的 reducer。
- 使用
redux-saga
创建 saga 中间件。 - 将创建的 reducer 和 saga 中间件注入到 Redux store 中。
- 调用
- 运行
app._router
函数,获取 React 根组件。 - 调用
ReactDOM.render
方法,将根组件渲染到页面中。 - 订阅 Redux store 的变更事件。
- 当 store 发生变更时,触发页面重新渲染。
- 调用
以上是 dva 从定义到运行的完整调用流程。通过这种流程,dva 实现了 model、router 的定义和注入,以及 Redux store 的创建和订阅。同时,通过集成 dva-loading
和 dva-immer
等插件,dva 还提供了 loading 状态管理和不可变数据更新的功能。