### Is it production ready?
Sure! We have 1000+ projects using dva in Alibaba.
### Does it support IE8?
No.
## Next
Some basic articles.
* The [8 Concepts](https://github.com/dvajs/dva/blob/master/docs/Concepts.md), and know how they are connected together
* [dva APIs](https://github.com/dvajs/dva/blob/master/docs/API.md)
* Checkout [dva knowledgemap](https://github.com/dvajs/dva-knowledgemap), including all the basic knowledge with ES6, React, dva
* Checkout [more FAQ](https://github.com/dvajs/dva/issues?q=is%3Aissue+is%3Aclosed+label%3Afaq)
* If your project is created by [dva-cli](https://github.com/dvajs/dva-cli), checkout how to [Configure it](https://github.com/sorrycc/roadhog#configuration)
Want more?
* 看看 dva 的前身 [React + Redux 最佳实践](https://github.com/sorrycc/blog/issues/1),知道 dva 是怎么来的
* 在 gitc 分享 dva 的 PPT :[React 应用框架在蚂蚁金服的实践](http://slides.com/sorrycc/dva)
* 如果还在用 dva@1.x,请尽快 [升级到 2.x](https://github.com/sorrycc/blog/issues/48)
## Community
| Slack Group | Github Issue | 钉钉群 | 微信群 |
| ------------------------------------------------------------ | ------------------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
| [sorrycc.slack.com](https://join.slack.com/t/sorrycc/shared_invite/enQtNTUzMTYxNDQ5MzE4LTg1NjEzYWUwNDQzMWU3YjViYjcyM2RkZDdjMzE0NzIxMTg3MzIwMDM2YjUwNTZkNDdhNTY5ZTlhYzc1Nzk2NzI) | [umijs/umi/issues](https://github.com/umijs/umi/issues) |
|
|
## License
[MIT](https://tldrlegal.com/license/mit-license)
================================================
FILE: README_zh-CN.md
================================================
[English](./README.md) | 简体中文
# dva
[](https://codecov.io/gh/dvajs/dva)
[](https://circleci.com/gh/dvajs/dva)
[](https://npmjs.org/package/dva)
[](https://travis-ci.org/dvajs/dva)
[](https://coveralls.io/r/dvajs/dva)
[](https://npmjs.org/package/dva)
[](https://david-dm.org/dvajs/dva)
[](https://gitter.im/dvajs/Lobby?utm_source=share-link&utm_medium=link&utm_campaign=share-link)
基于 [redux](https://github.com/reactjs/redux)、[redux-saga](https://github.com/redux-saga/redux-saga) 和 [react-router](https://github.com/ReactTraining/react-router) 的轻量级前端框架。(Inspired by [elm](http://elm-lang.org/) and [choo](https://github.com/yoshuawuyts/choo))
---
## 特性
* **易学易用**,仅有 6 个 api,对 redux 用户尤其友好,**[配合 umi 使用](https://umijs.org/guide/with-dva.html)后更是降低为 0 API**
* **elm 概念**,通过 reducers, effects 和 subscriptions 组织 model
* **插件机制**,比如 [dva-loading](https://github.com/dvajs/dva/tree/master/packages/dva-loading) 可以自动处理 loading 状态,不用一遍遍地写 showLoading 和 hideLoading
* **支持 HMR**,基于 [babel-plugin-dva-hmr](https://github.com/dvajs/babel-plugin-dva-hmr) 实现 components、routes 和 models 的 HMR
## 快速上手
请参考 [docs 目录](./docs) 获取指南和 API 参考。
## 他是怎么来的?
* [Why dva and what's dva](https://github.com/dvajs/dva/issues/1)
* [支付宝前端应用架构的发展和选择](https://www.github.com/sorrycc/blog/issues/6)
## 例子
* [Count](https://stackblitz.com/edit/dva-example-count): 简单计数器
* [User Dashboard](https://github.com/dvajs/dva/tree/master/examples/user-dashboard): 用户管理
* [AntDesign Pro](https://github.com/ant-design/ant-design-pro):([Demo](https://preview.pro.ant.design/)),开箱即用的中台前端/设计解决方案
* [HackerNews](https://github.com/dvajs/dva-hackernews): ([Demo](https://dvajs.github.io/dva-hackernews/)),HackerNews Clone
* [antd-admin](https://github.com/zuiidea/antd-admin): ([Demo](http://antd-admin.zuiidea.com/)),基于 antd 和 dva 的后台管理应用
* [github-stars](https://github.com/sorrycc/github-stars): ([Demo](http://sorrycc.github.io/github-stars/#/?_k=rmj86f)),Github Star 管理应用
* [Account System](https://github.com/yvanwangl/AccountSystem.git): 小型库存管理系统
* [react-native-dva-starter](https://github.com/nihgwu/react-native-dva-starter): 集成了 dva 和 react-navigation 典型应用场景的 React Native 实例
## FAQ
### 命名由来?
> D.Va拥有一部强大的机甲,它具有两台全自动的近距离聚变机炮、可以使机甲飞跃敌人或障碍物的推进器、 还有可以抵御来自正面的远程攻击的防御矩阵。
—— 来自 [守望先锋](http://ow.blizzard.cn/heroes/overwatch-dva) 。
### 是否可用于生产环境?
当然!公司内用于生产环境的项目估计已经有 1000+ 。
### 是否支持 IE8 ?
不支持。
## 下一步
以下能帮你更好地理解和使用 dva :
* 理解 dva 的 [8 个概念](./docs/Concepts.md) ,以及他们是如何串起来的
* 掌握 dva 的[所有 API](./docs/API.md)
* 查看 [dva 知识地图](./docs/knowledgemap/README.md) ,包含 ES6, React, dva 等所有基础知识
* 查看 [更多 FAQ](https://github.com/dvajs/dva/issues?q=is%3Aissue+is%3Aclosed+label%3Afaq),看看别人通常会遇到什么问题
* 如果你基于 dva-cli 创建项目,最好了解他的 [配置方式](https://github.com/sorrycc/roadhog/blob/master/README_zh-cn.md#配置)
还要了解更多?
* 看看 dva 的前身 [React + Redux 最佳实践](https://github.com/sorrycc/blog/issues/1),知道 dva 是怎么来的
* 在 gitc 分享 dva 的 PPT :[React 应用框架在蚂蚁金服的实践](http://slides.com/sorrycc/dva)
* 如果还在用 dva@1.x,请尽快 [升级到 2.x](https://github.com/sorrycc/blog/issues/48)
## 社区
| Slack Group | Github Issue | 钉钉群 | 微信群 |
| ------------------------------------------------------------ | ------------------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
| [sorrycc.slack.com](https://join.slack.com/t/sorrycc/shared_invite/enQtNTUzMTYxNDQ5MzE4LTg1NjEzYWUwNDQzMWU3YjViYjcyM2RkZDdjMzE0NzIxMTg3MzIwMDM2YjUwNTZkNDdhNTY5ZTlhYzc1Nzk2NzI) | [umijs/umi/issues](https://github.com/umijs/umi/issues) |
|
|
## License
[MIT](https://tldrlegal.com/license/mit-license)
================================================
FILE: docs/.vuepress/config.js
================================================
module.exports = {
title: 'DvaJS',
description: 'React and redux based, lightweight and elm-style framework.',
themeConfig: {
repo: 'dvajs/dva',
lastUpdated: 'Last Updated',
editLinks: true,
editLinkText: '在 GitHub 上编辑此页',
docsDir: 'docs',
nav: [
{ text: '指南', link: '/guide/' },
{ text: 'API', link: '/api/' },
{ text: '知识地图', link: '/knowledgemap/' },
{ text: '发布日志', link: 'https://github.com/dvajs/dva/releases' },
],
sidebar: {
'/guide/': [
{
title: '指南',
collapsable: false,
children: [
'',
'getting-started',
'examples-and-boilerplates',
'concepts',
'introduce-class',
],
},
{
title: '社区',
collapsable: false,
children: ['fig-show', 'develop-complex-spa', 'source-code-explore'],
},
],
'/api/': [''],
'/knowledgemap/': [''],
},
},
};
================================================
FILE: docs/.vuepress/override.styl
================================================
$accentColor = #fc54c3
$textColor = #2c3e50
$borderColor = #eaecef
$codeBgColor = #282c34
================================================
FILE: docs/API.md
================================================
# API
## Export Files
### dva
Default export file.
### dva/router
Export the api of [react-router@4.x](https://github.com/ReactTraining/react-router), and also export [react-router-redux](https://github.com/reactjs/react-router-redux) with the `routerRedux` key.
e.g.
```js
import { Router, Route, routerRedux } from 'dva/router';
```
### dva/fetch
Async request library, export the api of [isomorphic-fetch](https://github.com/matthew-andrews/isomorphic-fetch). It's just for convenience, you can choose other libraries for free.
### dva/saga
Export the api of [redux-saga](https://github.com/yelouafi/redux-saga).
### dva/dynamic
Util method to load React Component and dva model dynamically.
e.g.
```js
import dynamic from 'dva/dynamic';
const UserPageComponent = dynamic({
app,
models: () => [
import('./models/users'),
],
component: () => import('./routes/UserPage'),
});
```
`opts` include:
* app: dva instance
* models: function which return promise, and the promise return dva model
* component:function which return promise, and the promise return React Component
## dva API
### `app = dva(opts)`
Create app, and return dva instance. (Notice: dva support multiple instances.)
`opts` includes:
* `history`: Specify the history for router, default `hashHistory`
* `initialState`: Specify the initial state, default `{}`, it's priority is higher then model state
e.g. use `browserHistory`:
```js
import createHistory from 'history/createBrowserHistory';
const app = dva({
history: createHistory(),
});
```
Besides, for convenience, we can configure [hooks](#appusehooks) in `opts`, like this:
```js
const app = dva({
history,
initialState,
onError,
onAction,
onStateChange,
onReducer,
onEffect,
onHmr,
extraReducers,
extraEnhancers,
});
```
### `app.use(hooks)`
Specify hooks or register plugin. (Plugin return hooks finally.)
e.g. register [dva-loading](https://github.com/dvajs/dva-loading) plugin:
```js
import createLoading from 'dva-loading';
...
app.use(createLoading(opts));
```
`hooks` includes:
#### `onError((err, dispatch) => {})`
Triggered when `effect` has error or `subscription` throw error with `done`. Used for managing global error.
Notice: `subscription`'s error must be throw with the send argument `done`. e.g.
```js
app.model({
subscriptions: {
setup({ dispatch }, done) {
done(e);
},
},
});
```
If we are using antd, the most simple error handle would be like this:
```js
import { message } from 'antd';
const app = dva({
onError(e) {
message.error(e.message, /* duration */3);
},
});
```
#### `onAction(fn | fn[])`
Triggered when action is dispatched. Used for register redux middleware.
e.g. use [redux-logger](https://github.com/evgenyrodionov/redux-logger) to log actions:
```js
import createLogger from 'redux-logger';
const app = dva({
onAction: createLogger(opts),
});
```
#### `onStateChange(fn)`
Triggered when `state` changes. Used for sync `state` to localStorage or server and so on.
#### `onReducer(fn)`
Wrap reducer execute.
e.g. use [redux-undo](https://github.com/omnidan/redux-undo) to implement redo/undo:
```js
import undoable from 'redux-undo';
const app = dva({
onReducer: reducer => {
return (state, action) => {
const undoOpts = {};
const newState = undoable(reducer, undoOpts)(state, action);
// 由于 dva 同步了 routing 数据,所以需要把这部分还原
return { ...newState, routing: newState.present.routing };
},
},
});
```
#### `onEffect(fn)`
Wrap effect execute.
e.g. [dva-loading](https://github.com/dvajs/dva-loading) has implement auto loading state with this hook.
#### `onHmr(fn)`
HMR(Hot Module Replacement) related, currently used in [babel-plugin-dva-hmr](https://github.com/dvajs/babel-plugin-dva-hmr).
#### `extraReducers`
Specify extra reducers.
e.g. [redux-form](https://github.com/erikras/redux-form) needs extra `form` reducer:
```js
import { reducer as formReducer } from 'redux-form'
const app = dva({
extraReducers: {
form: formReducer,
},
});
```
#### `extraEnhancers`
Specify extra [StoreEnhancer](https://github.com/reactjs/redux/blob/master/docs/Glossary.md#store-enhancer)s.
e.g. use dva with [redux-persist](https://github.com/rt2zz/redux-persist):
```js
import { persistStore, autoRehydrate } from 'redux-persist';
const app = dva({
extraEnhancers: [autoRehydrate()],
});
persistStore(app._store);
```
### `app.model(model)`
Register model, view [#Model](#model) for details.
### `app.unmodel(namespace)`
Unregister model.
### `app.replaceModel(model)`
> Only available after `app.start()` got called
Replace an existing model with a new one, comparing by the namespace. If no one matches, add the new one.
After called, old `reducers`, `effects`, `subscription` will be replaced with the new ones, while original state is kept, which means it's useful for HMR.
### `app.router(({ history, app }) => RouterConfig)`
Register router config.
e.g.
```js
import { Router, Route } from 'dva/router';
app.router(({ history }) => {
return (
## Models
### State
`type State = any`
The state tree of your models. Usually, the state is a JavaScript object (although technically it can be any type) which is immutable data.
In dva, you can access top state tree data by `_store`.
```javascript
const app = dva();
console.log(app._store); // top state
```
### Action
`type AsyncAction = any`
Just like Redux's Action, in dva, action is a plain object that represents an intention to change the state. Actions are the only way to get data into the store. Any data, whether from UI events, network callbacks, or other sources such as WebSockets needs to eventually be dispatched as actions.action. (PS: dispatch is realized through props by connecting components.)
```javascript
dispatch({
type: 'add',
});
```
### dispatch function
`type dispatch = (a: Action) => Action`
A dispatching function (or simply dispatch function) is a function that accepts an action or an async action; it then may or may not dispatch one or more actions to the store.
Dispatching function is a function for triggering action, action is the only way to change state, but it just describes an action. while dispatch can be regarded as a way to trigger this action, and Reducer is to describe how to change state.
```javascript
dispatch({
type: 'user/add', // if in model outside, need to add namespace
payload: {},
});
```
### Reducer
`type Reducer
|
|
[https://t.me/joinchat/G0DdHw9tDZC-_NmdKY2jYg](https://t.me/joinchat/G0DdHw9tDZC-_NmdKY2jYg)
================================================
FILE: docs/api/README.md
================================================
---
sidebarDepth: 2
---
# API
## 输出文件
### dva
默认输出文件。
### dva/router
默认输出 [react-router](https://github.com/ReactTraining/react-router) 接口, [react-router-redux](https://github.com/reactjs/react-router-redux) 的接口通过属性 routerRedux 输出。
比如:
```js
import { Router, Route, routerRedux } from 'dva/router';
```
### dva/fetch
异步请求库,输出 [isomorphic-fetch](https://github.com/matthew-andrews/isomorphic-fetch) 的接口。不和 dva 强绑定,可以选择任意的请求库。
### dva/saga
输出 [redux-saga](https://github.com/yelouafi/redux-saga) 的接口,主要用于用例的编写。(用例中需要用到 effects)
### dva/dynamic
解决组件动态加载问题的 util 方法。
比如:
```js
import dynamic from 'dva/dynamic';
const UserPageComponent = dynamic({
app,
models: () => [
import('./models/users'),
],
component: () => import('./routes/UserPage'),
});
```
`opts` 包含:
* app: dva 实例,加载 models 时需要
* models: 返回 Promise 数组的函数,Promise 返回 dva model
* component:返回 Promise 的函数,Promise 返回 React Component
## dva API
### `app = dva(opts)`
创建应用,返回 dva 实例。(注:dva 支持多实例)
`opts` 包含:
* `history`:指定给路由用的 history,默认是 `hashHistory`
* `initialState`:指定初始数据,优先级高于 model 中的 state,默认是 `{}`
如果要配置 history 为 `browserHistory`,可以这样:
```js
import createHistory from 'history/createBrowserHistory';
const app = dva({
history: createHistory(),
});
```
另外,出于易用性的考虑,`opts` 里也可以配所有的 [hooks](#appusehooks) ,下面包含全部的可配属性:
```js
const app = dva({
history,
initialState,
onError,
onAction,
onStateChange,
onReducer,
onEffect,
onHmr,
extraReducers,
extraEnhancers,
});
```
### `app.use(hooks)`
配置 hooks 或者注册插件。(插件最终返回的是 hooks )
比如注册 [dva-loading](https://github.com/dvajs/dva-loading) 插件的例子:
```js
import createLoading from 'dva-loading';
...
app.use(createLoading(opts));
```
`hooks` 包含:
#### `onError((err, dispatch) => {})`
`effect` 执行错误或 `subscription` 通过 `done` 主动抛错时触发,可用于管理全局出错状态。
注意:`subscription` 并没有加 `try...catch`,所以有错误时需通过第二个参数 `done` 主动抛错。例子:
```js
app.model({
subscriptions: {
setup({ dispatch }, done) {
done(e);
},
},
});
```
如果我们用 antd,那么最简单的全局错误处理通常会这么做:
```js
import { message } from 'antd';
const app = dva({
onError(e) {
message.error(e.message, /* duration */3);
},
});
```
#### `onAction(fn | fn[])`
在 action 被 dispatch 时触发,用于注册 redux 中间件。支持函数或函数数组格式。
例如我们要通过 [redux-logger](https://github.com/evgenyrodionov/redux-logger) 打印日志:
```js
import createLogger from 'redux-logger';
const app = dva({
onAction: createLogger(opts),
});
```
#### `onStateChange(fn)`
`state` 改变时触发,可用于同步 `state` 到 localStorage,服务器端等。
#### `onReducer(fn)`
封装 reducer 执行。比如借助 [redux-undo](https://github.com/omnidan/redux-undo) 实现 redo/undo :
```js
import undoable from 'redux-undo';
const app = dva({
onReducer: reducer => {
return (state, action) => {
const undoOpts = {};
const newState = undoable(reducer, undoOpts)(state, action);
// 由于 dva 同步了 routing 数据,所以需要把这部分还原
return { ...newState, routing: newState.present.routing };
},
},
});
```
#### `onEffect(fn)`
封装 effect 执行。比如 [dva-loading](https://github.com/dvajs/dva-loading) 基于此实现了自动处理 loading 状态。
#### `onHmr(fn)`
热替换相关,目前用于 [babel-plugin-dva-hmr](https://github.com/dvajs/babel-plugin-dva-hmr) 。
#### `extraReducers`
指定额外的 reducer,比如 [redux-form](https://github.com/erikras/redux-form) 需要指定额外的 `form` reducer:
```js
import { reducer as formReducer } from 'redux-form'
const app = dva({
extraReducers: {
form: formReducer,
},
});
```
#### `extraEnhancers`
指定额外的 [StoreEnhancer](https://github.com/reactjs/redux/blob/master/docs/Glossary.md#store-enhancer) ,比如结合 [redux-persist](https://github.com/rt2zz/redux-persist) 的使用:
```js
import { persistStore, autoRehydrate } from 'redux-persist';
const app = dva({
extraEnhancers: [autoRehydrate()],
});
persistStore(app._store);
```
### `app.model(model)`
注册 model,详见 [#Model](#model) 部分。
### `app.unmodel(namespace)`
取消 model 注册,清理 reducers, effects 和 subscriptions。subscription 如果没有返回 unlisten 函数,使用 `app.unmodel` 会给予警告⚠️。
### `app.replaceModel(model)`
> 只在app.start()之后可用
替换model为新model,清理旧model的reducers, effects 和 subscriptions,但会保留旧的state状态,对于HMR非常有用。subscription 如果没有返回 unlisten 函数,使用 `app.unmodel` 会给予警告⚠️。
如果原来不存在相同namespace的model,那么执行`app.model`操作
### `app.router(({ history, app }) => RouterConfig)`
注册路由表。
通常是这样的:
```js
import { Router, Route } from 'dva/router';
app.router(({ history }) => {
return (
================================================
FILE: docs/guide/concepts.md
================================================
---
sidebarDepth: 2
---
# Dva 概念
## 数据流向
数据的改变发生通常是通过用户交互行为或者浏览器行为(如路由跳转等)触发的,当此类行为会改变数据的时候可以通过 `dispatch` 发起一个 action,如果是同步行为会直接通过 `Reducers` 改变 `State` ,如果是异步行为(副作用)会先触发 `Effects` 然后流向 `Reducers` 最终改变 `State`,所以在 dva 中,数据流向非常清晰简明,并且思路基本跟开源社区保持一致(也是来自于开源社区)。
## Models
### State
`type State = any`
State 表示 Model 的状态数据,通常表现为一个 javascript 对象(当然它可以是任何值);操作的时候每次都要当作不可变数据(immutable data)来对待,保证每次都是全新对象,没有引用关系,这样才能保证 State 的独立性,便于测试和追踪变化。
在 dva 中你可以通过 dva 的实例属性 `_store` 看到顶部的 state 数据,但是通常你很少会用到:
```javascript
const app = dva();
console.log(app._store); // 顶部的 state 数据
```
### Action
`type AsyncAction = any`
Action 是一个普通 javascript 对象,它是改变 State 的唯一途径。无论是从 UI 事件、网络回调,还是 WebSocket 等数据源所获得的数据,最终都会通过 dispatch 函数调用一个 action,从而改变对应的数据。action 必须带有 `type` 属性指明具体的行为,其它字段可以自定义,如果要发起一个 action 需要使用 `dispatch` 函数;需要注意的是 `dispatch` 是在组件 connect Models以后,通过 props 传入的。
```
dispatch({
type: 'add',
});
```
### dispatch 函数
`type dispatch = (a: Action) => Action`
dispatching function 是一个用于触发 action 的函数,action 是改变 State 的唯一途径,但是它只描述了一个行为,而 dipatch 可以看作是触发这个行为的方式,而 Reducer 则是描述如何改变数据的。
在 dva 中,connect Model 的组件通过 props 可以访问到 dispatch,可以调用 Model 中的 Reducer 或者 Effects,常见的形式如:
```javascript
dispatch({
type: 'user/add', // 如果在 model 外调用,需要添加 namespace
payload: {}, // 需要传递的信息
});
```
### Reducer
`type Reducer
这里显示 Son 组件的内容
这里显示 Son 组件的内容:{this.state.son}
## 核心概念
* State:一个对象,保存整个应用状态
* View:React 组件构成的视图层
* Action:一个对象,描述事件
* connect 方法:一个函数,绑定 State 到 View
* dispatch 方法:一个函数,发送 Action 到 State
## State 和 View
State 是储存数据的地方,收到 Action 以后,会更新数据。
View 就是 React 组件构成的 UI 层,从 State 取数据后,渲染成 HTML 代码。只要 State 有变化,View 就会自动更新。
## Action
Action 是用来描述 UI 层事件的一个对象。
```js
{
type: 'click-submit-button',
payload: this.form.data
}
```
## connect 方法
connect 是一个函数,绑定 State 到 View。
```js
import { connect } from 'dva';
function mapStateToProps(state) {
return { todos: state.todos };
}
connect(mapStateToProps)(App);
```
connect 方法返回的也是一个 React 组件,通常称为容器组件。因为它是原始 UI 组件的容器,即在外面包了一层 State。
connect 方法传入的第一个参数是 mapStateToProps 函数,mapStateToProps 函数会返回一个对象,用于建立 State 到 Props 的映射关系。
## dispatch 方法
dispatch 是一个函数方法,用来将 Action 发送给 State。
```js
dispatch({
type: 'click-submit-button',
payload: this.form.data
})
```
dispatch 方法从哪里来?被 connect 的 Component 会自动在 props 中拥有 dispatch 方法。
> connect 的数据从哪里来?
## dva 应用的最简结构(带 model)
```js
// 创建应用
const app = dva();
// 注册 Model
app.model({
namespace: 'count',
state: 0,
reducers: {
add(state) { return state + 1 },
},
effects: {
*addAfter1Second(action, { call, put }) {
yield call(delay, 1000);
yield put({ type: 'add' });
},
},
});
// 注册视图
app.router(() =>
## 数据流图 2
## app.model
dva 提供 app.model 这个对象,所有的应用逻辑都定义在它上面。
```js
const app = dva();
// 新增这一行
app.model({ /**/ });
app.router(() =>
================================================
FILE: docs/guide/source-code-explore.md
================================================
# Dva 源码解析
> 作者:杨光
## 隐藏在 package.json 里的秘密
随便哪个 dva 的项目,只要敲入 npm start 就可以运行启动。之前敲了无数次我都没有在意,直到我准备研究源码的时候才意识到:**在敲下这行命令的时候,到底发生了什么呢?**
答案要去 package.json 里去寻找。
>有位技术大牛曾经告诉过我:看源码之前,先去看 package.json 。看看项目的入口文件,翻翻它用了哪些依赖,对项目便有了大致的概念。
package.json 里是这么写的:
```json
"scripts": {
"start": "roadhog server"
},
```
翻翻依赖,`"roadhog": "^0.5.2"`。
既然能在 devDependencies 找到,那么肯定也能在 [npm](https://www.npmjs.com/package/roadhog) 上找到。原来是个和 webpack 相似的库,而且作者看着有点眼熟...
如果说 dva 是亲女儿,那 [roadhog](https://github.com/sorrycc/roadhog.git) 就是亲哥哥了,起的是 webpack 自动打包和热更替的作用。
在 roadhog 的默认配置里有这么一条信息:
```json
{
"entry": "src/index.js",
}
```
后转了一圈,启动的入口回到了 `src/index.js`。
## `src/index.js`
在 `src/index.js` 里,dva 一共做了这么几件事:
0. 从 'dva' 依赖中引入 dva :`import dva from 'dva'`;
1. 通过函数生成一个 app 对象:`const app = dva()`;
2. 加载插件:`app.use({})`;
3. 注入 model:`app.model(require('./models/example'))`;
4. 添加路由:`app.router(require('./routes/indexAnother'))`;
5. 启动:app.start('#root');
在这 6 步当中,dva 完成了 `使用 React 解决 view 层`、`redux 管理 model `、`saga 解决异步`的主要功能。事实上在我查阅资料以及回忆用过的脚手架时,发现目前端框架之所以被称为“框架”也就是解决了这些事情。前端工程师至今所做的事情都是在 **分离动态的 data 和静态的 view** ,只不过侧重点和实现方式也不同。
至今为止出了这么多框架,但是前端 MVX 的思想一直都没有改变。
# dva
## 寻找 “dva”
既然 dva 是来自于 `dva`,那么 dva 是什么这个问题自然要去 dva 的[源码](https://github.com/dvajs/dva)中寻找了。
> 剧透:dva 是个函数,返回一了个 app 的对象。
> 剧透2:目前 dva 的源码核心部分包含两部分,`dva` 和 `dva-core`。前者用高阶组件 React-redux 实现了 view 层,后者是用 redux-saga 解决了 model 层。
老规矩,还是先翻 package.json 。
引用依赖很好的说明了 dva 的功能:统一 view 层。
```json
// dva 使用的依赖如下:
"babel-runtime": "^6.26.0", // 一个编译后文件引用的公共库,可以有效减少编译后的文件体积
"dva-core": "^1.1.0", // dva 另一个核心,用于处理数据层
"global": "^4.3.2", // 用于提供全局函数的引用
"history": "^4.6.3", // browserHistory 或者 hashHistory
"invariant": "^2.2.2", // 一个有趣的断言库
"isomorphic-fetch": "^2.2.1", // 方便请求异步的函数,dva 中的 fetch 来源
"react-async-component": "^1.0.0-beta.3", // 组件懒加载
"react-redux": "^5.0.5", // 提供了一个高阶组件,方便在各处调用 store
"react-router-dom": "^4.1.2", // router4,终于可以像写组件一样写 router 了
"react-router-redux": "5.0.0-alpha.6",// redux 的中间件,在 provider 里可以嵌套 router
"redux": "^3.7.2" // 提供了 store、dispatch、reducer
```
不过 script 没有给太多有用的信息,因为 `ruban build` 中的 `ruban` 显然是个私人库(虽然在 tnpm 上可以查到但是也是私人库)。但根据惯例,应该是 dva 包下的 `index.js` 文件提供了对外调用:
```js
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = require('./lib');
exports.connect = require('react-redux').connect;
```
显然这个 `exports.default` 就是我们要找的 dva,但是源码中没有 `./lib` 文件夹。当然直接看也应该看不懂,因为一般都是使用 babel 的命令 `babel src -d libs` 进行编译后生成的,所以直接去看 `src/index.js` 文件。
## `src/index.js`
`src/index.js`[在此](https://github.com/dvajs/dva/blob/master/packages/dva/src/index.js) :
在这里,dva 做了三件比较重要的事情:
1. 使用 call 给 dva-core 实例化的 app(这个时候还只有数据层) 的 start 方法增加了一些新功能(或者说,通过代理模式给 model 层增加了 view 层)。
2. 使用 react-redux 完成了 react 到 redux 的连接。
3. 添加了 redux 的中间件 react-redux-router,强化了 history 对象的功能。
### 使用 call 方法实现代理模式
dva 中实现代理模式的方式如下:
**1. 新建 function ,函数内实例化一个 app 对象。**
**2. 新建变量指向该对象希望代理的方法, `oldStart = app.start`。**
**3. 新建同名方法 start,在其中使用 call,指定 oldStart 的调用者为 app。**
**4. 令 app.start = start,完成对 app 对象的 start 方法的代理。**
上代码:
```js
export default function(opts = {}) {
// ...初始化 route ,和添加 route 中间件的方法。
/**
* 1. 新建 function ,函数内实例化一个 app 对象。
*
*/
const app = core.create(opts, createOpts);
/**
* 2. 新建变量指向该对象希望代理的方法
*
*/
const oldAppStart = app.start;
app.router = router;
/**
* 4. 令 app.start = start,完成对 app 对象的 start 方法的代理。
* @type {[type]}
*/
app.start = start;
return app;
// router 赋值
/**
* 3.1 新建同名方法 start,
*
*/
function start(container) {
// 合法性检测代码
/**
* 3.2 在其中使用 call,指定 oldStart 的调用者为 app。
*/
oldAppStart.call(app);
// 因为有 3.2 的执行才有现在的 store
const store = app._store;
// 使用高阶组件创建视图
}
}
```
> 为什么不直接在 start 方式中 oldAppStart ?
- 因为 dva-core 的 start 方法里有用到 this,不用 call 指定调用者为 app 的话,oldAppStart() 会找错对象。
> 实现代理模式一定要用到 call 吗?
- 不一定,看有没有 使用 this 或者代理的函数是不是箭头函数。从另一个角度来说,如果使用了 function 关键字又在内部使用了 this,那么一定要用 call/apply/bind 指定 this。
> 前端还有那里会用到 call ?
- 就实际开发来讲,因为已经使用了 es6 标准,基本和 this 没什么打交道的机会。使用 class 类型的组件中偶尔还会用到 this.xxx.bind(this),stateless 组件就洗洗睡吧(因为压根没有 this)。如果实现代理,可以使用继承/反向继承的方法 —— 比如高阶组件。
### 使用 react-redux 的高阶组件传递 store
经过 call 代理后的 start 方法的主要作用,便是使用 react-redux 的 provider 组件将数据与视图联系了起来,生成 React 元素呈现给使用者。
不多说,上代码。
```js
// 使用 querySelector 获得 dom
if (isString(container)) {
container = document.querySelector(container);
invariant(
container,
`[app.start] container ${container} not found`,
);
}
// 其他代码
// 实例化 store
oldAppStart.call(app);
const store = app._store;
// export _getProvider for HMR
// ref: https://github.com/dvajs/dva/issues/469
app._getProvider = getProvider.bind(null, store, app);
// If has container, render; else, return react component
// 如果有真实的 dom 对象就把 react 拍进去
if (container) {
render(container, store, app, app._router);
// 热加载在这里
app._plugin.apply('onHmr')(render.bind(null, container, store, app));
} else {
// 否则就生成一个 react ,供外界调用
return getProvider(store, this, this._router);
}
// 使用高阶组件包裹组件
function getProvider(store, app, router) {
return extraProps => (
#### 理解 CSS Modules
一张图理解 CSS Modules 的工作原理:

`button` class 在构建之后会被重命名为 `ProductList_button_1FU0u` 。`button` 是 local name,而 `ProductList_button_1FU0u` 是 global name 。**你可以用简短的描述性名字,而不需要关心命名冲突问题。**
然后你要做的全部事情就是在 css/less 文件里写 `.button {...}`,并在组件里通过 `styles.button` 来引用他。
#### 定义全局 CSS
CSS Modules 默认是局部作用域的,想要声明一个全局规则,可用 `:global` 语法。
比如:
```css
.title {
color: red;
}
:global(.title) {
color: green;
}
```
然后在引用的时候:
```javascript
src/index.js and save to reload.
src/index.js and save to reload.