前言
由于之前写了几个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 (例子)
准备工作
Step1. 安装 dva-cli 并创建应用
先安装 dva-cli,并确保版本是 0.7.x。
$ npm i dva-cli@0.7 -g
$ dva -v
0.7.0
然后创建应用:
$ dva new dva-learning
创建成功后进入该文件夹:
$ cd dva-learning
`项目的目录结构`
dva_learning
|-----mock # => 前端模拟数据
|-----node_modules # => 项目依赖采用npm管理,所有包均在此目录。
|-----public # => 存放index.html
|-----src
|------assets # => 项目静态资源文件夹(图片等)
|------components # => 无状态组件文件夹
|------models # => 状态model文件夹
|------routes # => 路由配置文件夹,页面存放在该文件夹下
|------services # => 服务层文件夹
|------utils # => 工具函数文件夹
|------index.css # => 全局通用样式
|------index.js # => 单页引用入口js,dva项目初始化
|------router.js # => 全局路由状态管理文件
|-----package.json # => npm包管理文件
|-----.eslintrc # => 代码规范配置文件
|-----.roadhogrc # => 打包配置文件
|-----.roadhogrc.mock.js # => 模拟数据配置文件
|-----README.md
`项目的目录结构`
`运行项目`
$ npm start
如果运行成功的话,浏览器会自动弹出并访问8000端口,看到如下画面:
run_success@2x.pngStep2. 配置 antd 和 babel-plugin-import
antd是由蚂蚁金服开发的一套UI组件,具有学习成本低、上手速度快、实现效果好的特点。十分适合初学者并且与dva无缝接入。如需了解更多请查看 ANT DESIGN。
babel-plugin-import 用于按需引入 antd 的 JavaScript 和 CSS,这样打包出来的文件不至于太大。
$ npm i antd --save
$ npm i babel-plugin-import --save-dev
修改 .roadhogrc
,在 "extraBabelPlugins"
里加上:
["import", { "libraryName": "antd", "style": "css" }]
Step3. 添加新页面
我们的目标是写一个登录的界面,成功之后显示dva默认的首页。所以在src/routes文件夹下,新建Login.js和Login.css文件。js文件用来写组件布局,css文件用来写样式,默认为js文件和css文件一一对应。
在Login.js中:
`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);
为这个页面添加样式:
`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页面,将新写的登录页面,放到默认显示页面
`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;
然后运行,会看到你完成的页面
dva登录@2x.pngStep4. 添加事件
之前完成的页面还没有添加点击事件,接下来添加几行代码,让它可以编辑,可以输入和点击
页面中使用的Input, Icon, Button均是Antd中基本的组件,在 ANT DESIGN有对它们详细的介绍。
为Button添加一个单击事件
`点击button时触发`
const submit = () => {
alert('here')
};
`在Button中添加这个方法`
<Button type="primary" className={styles.button} onClick={submit}>
登录
</Button>
再次运行项目,点击登录按钮会显示
dva单击事件@2x.pngStep.5 处理逻辑
如果需要这个登录界面更加真实的话,需要处理一些登录中的逻辑,比如点击登录按钮的时候,判断输入框中是否输入了数据等。
dva中有专门的文件夹存放这些处理页面内逻辑的代码。查看src/models/example.js文件,这是一个标准的模版,每个处理逻辑的文件都包含下面几部分
export default {
namespace: 'example', # => 唯一标识,应用中唯一
state: {}, # => 需要存储的值,每次修改会刷新界面
subscriptions: { # => 订阅
setup({ dispatch, history }) {
},
},
effects: { # => 副作用,一般用来发起请求
*fetch({ payload }, { call, put }) {
yield put({ type: 'save' });
},
},
reducers: { # => 只有在这里才能修改state的值
save(state, action) {
return { ...state, ...action.payload };
},
},
};
更多关于dva的api,请查看dva APIs
在src/models中新建login.js文件
`login.js`
export default {
namespace: 'login',
state: {
loading: false,
},
subscriptions: {
},
effects: {
},
reducers: {
save(state, action) {
return { ...state, ...action.payload };
}
},
};
然后在Login.js中调用save方法
`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并添加:
`.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并添加:
`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中:
export default function request(url, method, params) {
if (method === 'POST') {
const formData = new FormData();
for (const key in params) {
if (params[key] != null) {
const value = params[key];
formData.append(key, value);
}
}
return fetch(url, {
method: method,
body: formData,
}).then(checkStatus)
.then(parseJSON)
.then(data => ({ data }))
.catch(err => ({ err }));
}
if (method === 'GET') {
return fetch(url)
.then(checkStatus)
.then(parseJSON)
.then(data => ({ data }))
.catch(err => ({ err }));
}
}
然后在services中新建login.js,这里可以理解为转发,从点击事件中传递到request里请求:
import request from '../utils/request';
export function login(params) {
console.log('services处理');
return request('/login', 'POST', params);
}
之后在models/login.js,发起这个请求:
effects: {
*fetch({ payload }, { call, put }) { // eslint-disable-line
if(!payload.userName || !payload.passWord) {
message.error('请输入账号密码');
return;
}
const { data } = yield call(service.login, payload);
},
},
最后在界面的button的点击事件中,调用models里的事件:
const submit = () => {
dispatch({
type: 'login/fetch',
payload: {
userName: login.user,
passWord: login.password,
},
});
};
所有的都完成之后,重启项目,点击button之后可以在命令行中看到:
dva的mock@2x.pngundefined的原因是,roadhog的版本问题,获取不到从前台传递过来的参数。
Step.7 完善细节
整体的流程已经完成,现在要为它添加一些细节,让它看起来更加的真实
添加一个加载等待的圈圈,在点击button的同时显示,后台反馈结果后消失并跳转到下一个界面。
<Spin spinning={login.loading}>
...
</Spin>
用Spin标签将其他的标签包起来,当它显示的时候,会出现在被包裹的标签之上。
通过控制login.loading来控制Spin的显示与消失。
reducers: {
...
loadingShow(state) {
return { ...state, loading: true};
},
loadingHide(state) {
return { ...state, loading: false};
}
},
最后在请求的时候完成这个流程
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中订阅:
subscriptions: {
setup({ dispatch, history }) {
history.listen((location) => {
if (location.pathname === '/') {
message.info('进入了登录页');
}
});
},
},
总结
这个登录页面,展示了从页面到请求的整个过程,虽然看起来有点绕,涉及了很多的页面。但�是当文件多了,就会体现出dva这样分层的好处:各个文件夹各司其职,功能单一。
最后实现的效果:
dva-learning.gif
网友评论