dva.js 简介
- dva 是阿里前端架构师 sorrycc 带 team 研发的一套轻量级前端框架,其目的是尽量避免前端重复性劳动,简化开发流程。
一个完整的 dva 脚手架应该包含以下内容:- 自动创建一个包含 package.json 的项目。
- 自动创建成体系的目录结构。
- 自动安装项目需要的基础包。
- 集成代码检查工具 ESLint。
- 集成模拟接口工具 Mock。
- 集成服务启动打包工具 Roadhog。
- 集成版本控制工具 Git。
初始化
- 安装
dva-cli
用于初始化项目:npm install dva-cli -g
- 创建项目目录,并进入该目录:
mkdir your-project cd your-project
- 初始化项目:
dva init
- 运行
npm start
运行徐项目。
目录结构
- 目录初始化以后,目录默认如下:
|- mock |- node_modules |- package.json |- public |- src |- asserts |- components |- models |- routes |- services |- utils |- router.js |- index.js |- index.css |- .editorconfig |- .eslintrc |- .gitignore |- .roadhogrc.mock.js |- .webpackrc
- mock 用于存放 mock 数据的文件
- public 一般用于存放静态文件,打包时会被直接复制到输出目录(./dist)
- src 文件夹用于存放项目源代码
- asserts 用于存放静态资源,打包时会经过 webpack 处理;
- components 用于存放 React 组件,一般是该项目公用的无状态组件;
- models 用于存放模型文件;
- routes 用于存放需要 connect model 的路由组件;
- services 用于存放服务文件,一般是网路请求等;
- utils 工具类库;
- routers.js 路由文件;
- index.js 项目的入口文件;
- index.css 一般是公用样式
- .editorconfig 编辑器配置文件
- .eslintrc ESLint配置文件
- .gitignore Git忽略文件
- .roadhogrc.mock.js Mock配置文件
- .webpackrc 自定义的webpack配置文件,JSON格式,如果需要JS格式,可修改为.webpackrc.js
antd按需引入
- 先安装
antd
和babel-plugin-import
:npm install antd balbel-plugin-import --save
babel-plugin-import
也可以通过-D
参数安装到devDependencies
中,它用于实现按需加载。
之后在.webpackrc
中添加如下配置:
现在就可以按需引入{ "extraBabelPlugins": [ ["import", { "libraryName": "antd", "libraryDirectory": "es", "style": true }] ] }
antd
的组件了,如:import { Button } from 'amtd';
,Button组件的样式文件也会自动引入。
更多.webpackrc
配置请参考 roadhog配置
自定义 antd 主题
-
可以在
.webpackrc
中添加theme
字段直接进行主题自定义,但是如果自定义的变量太多,建议单独提取出来,方便管理。建议在
./src
目录下新建名为theme.js
的文件,然后再.webpackrc
中引入,如下:{ "theme": "./src/theme.js" }
theme.js
文件如下:export default { "primary-color": "#000", }
更多可自定义的 antd 变量请参考 default.less
CSS Modules
- 使用 dva-cli 初始化的项目默认已经启用了 CSS Modules,如果不想使用CSS Modules,在
.webpackrc
中添加一下配置即可禁用:"disableCSSModules": true
开发代理
- 开发过程中如果需要代理API接口,在
webpackrc
中添加如下配置:{ "proxy": { "/api": { "target": "http://your-api-server", "changeOrigin": 'true } } }
Mock
- 入需 Mock 功能, 在
.webpackrc.mock.js
中配置即可,如:
如上配置,当请求export default { 'GET /api/users': { users: [{ username: 'admin' }] }, }
/api/users
时会返回 JSON 格式的数据。
同时也支持自定义函数,如下:
具体的 API 请参考 Express.js@4.export default { 'POST /api/users': (req, res) => { res.end('OK'); }, }
当 mock 数据太多是,可以拆分放到./mock
文件夹中,然后在.roadhogrc.mlck.js
中引入。
HMR
- HMR,即模块热替换,在修改代码后不需要刷新整个页面,方便开发时的调试。可以在
.webpackrc
中添加如下配置来使用HMR:
如果无效,请尝试更新一下{ "env": { "development": { "extraBabelPlugins": [ "dva-hmr" ] } } }
bebel-plugin-dva-hmr
。
env
字段是针对特定环境进行配置,因为 HMR 只在开发环境下使用,所以将配置添加到development
字段即可,运行npm run build
时的环境变量为production
。
组件动态加载
- dva内置了
dynamic
方法用于实现组件的动态加载,用法如下:
实际使用的时候,可以对其进行简单的封装,否则每个路由组件都这么写一遍很麻烦。import dynamic from 'dva/dynamic'; const UserPageComponent = dynamic({ app, models: () => [ import('./models/users'), ], component: () => import('./routes/UserPage'), });
dva-loading
- dva-loading 是一个用于处理 loading 状态的 dva 插件,基于 dva 的管理 effects 执行的 hook 实现,它会在 state 中添加哟个
loading
字段(该字段可自定义),自动处理网络请求的状态,不需要自己再去写showLoading
和hideLoading
方法。
在./src/index.js
中引入使用即可:import createLoading from 'dva-loading'; const app = dva(); app.use(createLoading(opts));
opts
仅有一个namespace
字段,默认为loading
。
Model
-
Model 是 dva 最重要的部分,可以理解为 redux、react-redux、 redux-saga 的封装。
通常一个项目中一个模块对应一个 model ,一个基本的 model 如下:
import { fetchUsers } from '../services/user'; export default { namespace: 'user', stat: { list: [], } reducers: { save(state, action) { return { ...state, list: action.data }; }, }, effects: { * fetch(action, { put, call }) { const users = yield put(fetchUsers, action.sata); yield put({ type: 'save', data: users }); }, }, subscriptions: { setup({ dispatch, history }) { return history.listen(({ pathname }) => { if (pathname === '/user') { dispatch({ type: 'fetch', }); } }); }, }, }
namespace
是该 model 的命名空间,同时也是全局state
上的一个属性,只能是字符串,不支持使用.
创建多层命名空间。state
是状态的初始值。reducer
类似于 redux 中的 reducer,它是一个纯函数,用于处理同步操作,是唯一可以修改state
的地方,由action
触发,它有state
和action
两个参数。effects
用于处理异步操作,不能直接修改state
,由action
触发, 也可以触发action
。它只能是generator
函数,并且有action
和effects
两个参数。第二个参数effects
包含put
、call
和select
三个字段,put
用于触发action
,类似与dispatch
,call
用于调用异步处理逻辑,select
用于从state
中获取数据。subscriptions
用于订阅某些数据源,并根据情况 dispatch 某些 action ,格式为({ dispatch, history }, done) => unlistenFunction
。
如上的一个model,监听路由变化,当进入 `/user` 页面时,执行 `effects` 中的 `fetch` ,以从服务端获取用户列表,然后 `fetch` 中触发 `reducers` 中的 `save` 将从服务器获取到的数据报讯到 `state` 中。
注意,在 model 中触发这个 model 是不需要写命名空间,比如在 `fetch` 中触发 `save` 时是 `{ type: 'save' }`。而在组件中触发 `action` 时就需要带上命名空间了,比如在某个组件中触发 `fetch` 时,应该是 `{ type: 'user/fetch }`。
- 动态加载model
有不少业务场景下,我们可能会定义很多个model
,但并不需要在应用启动的时候就全部加在,比较典型的是各类管理控制台,如果每个功能页面是通过路由切换,互相之间没有关系的话,通常会使用webpack的require.ensure
来做代码模块的懒加载。
我们也可以利用这个特性来做model
的动态加载.
function RouterConfig({ history, app }) {
const routes = [
{
path: '/',
name: 'IndexPage',
getComponent(nextState, cb) {
require.ensure([], (require) => {
registerModel(app, require('./models/dashboard'));
cb(null, require('./routes/IndexPage'));
});
},
},
{
path: '/users',
name: 'UsersPage',
getComponent(nextState, cb) {
require.ensure([], (require) => {
registerModel(app, require('./models/users'));
cb(null, require('./routes/Users'));
});
},
},
];
return <Router history={history} routes={routes} />;
}
app
-
在
./src/index.js
中可以看到如下代码:import dva from 'dva'; const app = dva();
app 就是 dva 的实例,创建实例时 dva 方法传入一些参数,如下:
- history
- initialstate
- onError
- onAction
- onStateChange
- onReducer
- onEffects
- onHmr
- extraReducers
- extraEnhancers
history 是给路由器用的,默认为
hashHistory
,如果想要使用 browserHistory,需要安装history
,然后在。/src/index.js
引入使用:import dva from 'dva'; impoer createHistory from 'history/createBroeserHistory'; const app = dva({ history: createHistory(), });
initialState 是 state 的初始化数据,优先级高于 model 中的state,默认为
{}
。
其他以on
开头的均为钩子函数。
connect
- 当写完 moedl 和组件之后,需要将 model 和组件连接起来。 dva 提供了
connect
的方法,其实它就是react-redux
的connect
。用法如下:
connect 后的组件除了可以取到import React from 'react'; import { connect } from 'dva'; const User = ({ dispatch, user }) => { return ( <div></div> ) } export default connect(({ user }) => { return user; })(User);
dispatch
和state
,还可以获取到location
和history
。
错误处理
-
effects
和subscriptions
抛出的错误都会经过onError
钩子函数,所以可以在onError
中进行全局错误处理。
如果需要对某些const app = dva({ onError(err, dispatch) { console.log(err); }, });
effects
进行特殊的错误处理,可以使用try catch
。
异步请求
-
dva 集成了
isomorphic-fetch
用于处理异步请求,并且使用 dva-cli 初始化的项目中,已经在./src/utils/request.js
中对 fetch 进行了简单的封装,可以在这里根据服务端 API 的数据结构进行统一的错误处理。当然,如果不想使用 fetch ,完全可以引入自己喜欢的第三方库,没有任何影响,打包时也不会将
isomorphic-fetch
打包进去。