前言
由于之前写了几个dva的项目,近期没怎么用有些遗忘了,写个小结记录一下。
dva是基于react、react-router、redux封装的一个轻框架。详细的介绍在 dva官网,这里仅仅摘录部分。项目托管在GitHub上,点击这里。
特性
- 易学易用:仅有 6 个 api,对 redux 用户尤其友好
- elm 概念:通过
reducers
, effects
和 subscriptions
组织 model
- 支持 mobile 和 react-native:跨平台 (react-native 例子)
- 支持 HMR:目前基于 babel-plugin-dva-hmr 支持 components、routes 和 models 的 HMR
- 动态加载 Model 和路由:按需加载加快访问速度 (例子)
- 插件机制:比如 dva-loading 可以自动处理 loading 状态,不用一遍遍地写 showLoading 和 hideLoading
- 完善的语法分析库 dva-ast:dva-cli 基于此实现了智能创建 model, router 等
- 支持 TypeScript:通过 d.ts (例子)
准备工作
- 确保 node 版本是 6.5 +
- 用 cnpm 或 yarn 能节约你安装依赖的时间
Step1. 安装 dva-cli 并创建应用
先安装 dva-cli,并确保版本是 0.7.x。
1 2 3
| $ npm i dva-cli@0.7 -g $ dva -v 0.7.0
|
然后创建应用:
创建成功后进入该文件夹:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| $ cd dva-learning `项目的目录结构` dva_learning |-----mock |-----node_modules |-----public |-----src |------assets |------components |------models |------routes |------services |------utils |------index.css |------index.js |------router.js |-----package.json |-----.eslintrc |-----.roadhogrc |-----.roadhogrc.mock.js |-----README.md `项目的目录结构` `运行项目` $ npm start
|
如果运行成功的话,浏览器会自动弹出并访问8000端口,看到如下画面:
antd是由蚂蚁金服开发的一套UI组件,具有学习成本低、上手速度快、实现效果好的特点。十分适合初学者并且与dva无缝接入。如需了解更多请查看 ANT DESIGN。
babel-plugin-import 用于按需引入 antd 的 JavaScript 和 CSS,这样打包出来的文件不至于太大。
1 2
| $ npm i antd $ npm i babel-plugin-import
|
修改 .roadhogrc
,在 "extraBabelPlugins"
里加上:
1
| ["import", { "libraryName": "antd", "style": "css" }]
|
Step3. 添加新页面
我们的目标是写一个登录的界面,成功之后显示dva默认的首页。所以在src/routes文件夹下,新建Login.js和Login.css文件。js文件用来写组件布局,css文件用来写样式,默认为js文件和css文件一一对应。
在Login.js中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| `Login.js` import React from 'react'; import { connect } from 'dva'; import { Input, Icon, Button } from 'antd'; import styles from './Login.css';
function Login() { return ( <div className={styles.inputDiv}> <div> <Input placeholder="用户名" prefix={<Icon type="user" />} size="large" className={styles.inputUser} /> </div> <div> <Input placeholder="密码" prefix={<Icon type="lock" />} size="large" className={styles.inputPass} /> </div> <Button type="primary" className={styles.button}> 登录 </Button> </div> ); }
Login.propTypes = { };
export default connect()(Login);
|
为这个页面添加样式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| `Login.css` .inputDiv { position: absolute; top: 50%; left: 50%; width: 200px; height: 220px; margin: -110px 0 0 -100px; } .inputUser { width: 200px; } .inputPass { width: 200px; margin-top: 20px; } .button { width: 200px; margin-top: 20px; }
|
然后修改router.js页面,将新写的登录页面,放到默认显示页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| `router.js` import React from 'react'; import { Router, Route } from 'dva/router'; import IndexPage from './routes/IndexPage'; import Login from './routes/Login';
function RouterConfig({ history }) { return ( <Router history={history}> <Route path="/" component={Login} /> <Route path="/home" component={IndexPage} /> </Router> ); }
export default RouterConfig;
|
然后运行,会看到你完成的页面
Step4. 添加事件
之前完成的页面还没有添加点击事件,接下来添加几行代码,让它可以编辑,可以输入和点击
页面中使用的Input, Icon, Button均是Antd中基本的组件,在 ANT DESIGN有对它们详细的介绍。
为Button添加一个单击事件
1 2 3 4 5 6 7 8
| `点击button时触发` const submit = () => { alert('here') } `在Button中添加这个方法` <Button type="primary" className={styles.button} onClick={submit}> 登录 </Button>
|
再次运行项目,点击登录按钮会显示
Step.5 处理逻辑
如果需要这个登录界面更加真实的话,需要处理一些登录中的逻辑,比如点击登录按钮的时候,判断输入框中是否输入了数据等。
dva中有专门的文件夹存放这些处理页面内逻辑的代码。查看src/models/example.js文件,这是一个标准的模版,每个处理逻辑的文件都包含下面几部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| export default {
namespace: 'example',
state: {},
subscriptions: { setup({ dispatch, history }) { }, },
effects: { *fetch({ payload }, { call, put }) { yield put({ type: 'save' }); }, },
reducers: { save(state, action) { return { ...state, ...action.payload }; }, },
};
|
更多关于dva的api,请查看dva APIs
在src/models中新建login.js文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| `login.js` export default {
namespace: 'login',
state: { loading: false, },
subscriptions: { },
effects: { },
reducers: { save(state, action) { return { ...state, ...action.payload }; } }, };
|
然后在Login.js中调用save方法
1 2 3 4 5 6 7 8 9
| `Login.js` const userName = (e) => { dispatch({ type: 'login/save', payload: { user: e.target.value, }, }); };
|
这个方法的意思是:当输入用户名的时候,调用save方法,并将输入的值,保存在state.user中。
Step.6 发送请求
输入完用户名以及密码之后,单击登录按钮,将输入的值发送至后台校验,校验通过之后跳转到下一个页面。
现在已经有了输入的用户名和密码,分别是login.user和login.password,现在需要将这两个数据发送到后台。由于现在并没有后台服务支持,dva支持mock数据,所以先在前台模拟一个后台服务。
在项目根目录下新建.roadhogrc.mock.js并添加:
1 2 3 4 5 6
| `.roadhogrc.mock.js` const mock = {} require('fs').readdirSync(require('path').join(__dirname + '/mock')).forEach(function(file) { Object.assign(mock, require('./mock/' + file)) }) module.exports = mock
|
之后在mock文件夹中新建login.js并添加:
1 2 3 4 5 6 7 8 9 10
| `login.js` const qs = require('qs');
module.exports = { 'POST /login' (req, res) { console.log(req.body); console.log('接受到请求'); setTimeout(()=>res.json({code:'200',message:'从mock/example.js请求成功'}),2000) }, };
|
这个的意思是说,监听本地的8000端口,当访问http://localhost:8000/login的时候,会延迟2秒并返回数据。
模拟的后台服务已经完成,现在要在button中添加点击事件,去请求这个接口。
首先改造一下fetch请求,在utils/request.js中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| export default function request(url, method, params)
} return fetch(url, { method: method, body: formData, }).then(checkStatus) .then(parseJSON) .then(data => ({ data })) .catch(err => ({ err })); } if (method === 'GET')
)) .catch(err => ({ err })); } }
|
然后在services中新建login.js,这里可以理解为转发,从点击事件中传递到request里请求:
1 2 3 4 5 6
| import request from '../utils/request';
export function login(params) { console.log('services处理'); return request('/login', 'POST', params); }
|
之后在models/login.js,发起这个请求:
1 2 3 4 5 6 7 8 9
| effects: { *fetch({ payload }, { call, put }) { if(!payload.userName || !payload.passWord) { message.error('请输入账号密码'); return; } const { data } = yield call(service.login, payload); }, },
|
最后在界面的button的点击事件中,调用models里的事件:
1 2 3 4 5 6 7 8 9
| const submit = () => { dispatch({ type: 'login/fetch', payload: { userName: login.user, passWord: login.password, }, }); };
|
所有的都完成之后,重启项目,点击button之后可以在命令行中看到:
undefined的原因是,roadhog的版本问题,获取不到从前台传递过来的参数。
Step.7 完善细节
整体的流程已经完成,现在要为它添加一些细节,让它看起来更加的真实
添加一个加载等待的圈圈,在点击button的同时显示,后台反馈结果后消失并跳转到下一个界面。
1 2 3
| <Spin spinning={login.loading}> ... </Spin>
|
用Spin标签将其他的标签包起来,当它显示的时候,会出现在被包裹的标签之上。
通过控制login.loading来控制Spin的显示与消失。
1 2 3 4 5 6 7 8 9
| reducers: { ... loadingShow(state) { return { ...state, loading: true}; }, loadingHide(state) { return { ...state, loading: false}; } },
|
最后在请求的时候完成这个流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| effects: { *fetch({ payload }, { call, put }) { if(!payload.userName || !payload.passWord) { message.error('请输入账号密码'); return; } yield put({ type: 'loadingShow' }); const { data } = yield call(service.login, payload); if(data.code === '200') { yield put({ type: 'loadingHide' }); browserHistory.push('/home'); }else { yield put({ type: 'loadingHide' }); message.error('登录失败'); } }, },
|
如果需要在显示登录页面之前执行某些操作,可以在subscriptions中订阅:
1 2 3 4 5 6 7 8 9
| subscriptions: { setup({ dispatch, history }) { history.listen((location) => { if (location.pathname === '/') { message.info('进入了登录页'); } }); }, },
|
总结
这个登录页面,展示了从页面到请求的整个过程,虽然看起来有点绕,涉及了很多的页面。但是当文件多了,就会体现出dva这样分层的好处:各个文件夹各司其职,功能单一。
最后实现的效果: