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 --savebabel-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打包进去。
