目录
一:分层
二:使用DVA进行数据分层管理
三:在model中请求数据
四:mock数据
一:分层
上图中,左侧是服务端代码的层次结构,由
Controller
、
Service
、
Data Access
三层组成服务端系统:
- Controller 层负责与用户直接打交道,渲染页面、提供接口等,侧重于展示型逻辑。
- Service 层负责处理业务逻辑,供 Controller 层调用。
- Data Access 层顾名思义,负责与数据源对接,进行纯粹的数据读写,供 Service 层调用。
上图的右侧是前端代码的结构,同样需要进行必要的分层:
- Page 负责与用户直接打交道:渲染页面、接受用户的操作输入,侧重于展示型交互性逻辑。
- Model 负责处理业务逻辑,为 Page 做数据、状态的读写、变换、暂存等。
- Service 负责与 HTTP 接口对接,进行纯粹的数据读写。
二:使用DVA进行数据分层管理
dva
是基于
redux
、
redux-saga
和
react-router
的轻量级前端框架。官网:
https://dvajs.com/
对于
dva
我们不做过多详细的讲解,我们只要做到能够使用起来就可以了。对于想要全面学习
dva
框架的同学可自 行研究。
首先,我们先将
dva
框架引入进来,由于
umi
对
dva
进行了整合,所以导入就变得非常简单了。
在confifig.js文件中进行配置:
export default {
plugins: [
['umi-plugin-react', {
dva: true // 开启dva功能
}]
]
};
接下来,创建
model
文件,在
umi
中,约定在
src/models
文件夹中定义
model
,所以,在该文件夹下创建
ListData.js
文件:
编写内容:
export default {
namespace: 'list',
state: {
data: [1, 2, 3],
maxNum: 3
}
}
下面对
List.js
进行改造:
import React from 'react';
import { connect } from 'dva';
const namespace = 'list';
const mapStateToProps = (state) => {
const listData = state[namespace].data;
return {
listData
};
};
@connect(mapStateToProps)
class List extends React.Component{
render(){
return (
<div>
<ul>
{
// 遍历值
this.props.listData.map((value,index) => {
return <li key={index}>{value}</li>
})
}
</ul>
<button
onClick={()=>{ //为按钮添加点击事件
// let maxNum = this.state.maxNum + 1;
// let list = [...this.state.dataList, maxNum];
// this.setState({ //更新状态值
// dataList : list,
// maxNum : maxNum
// });
}}>
添加</button>
</div>
);
}
}
export default List;
测试:
可以看到,效果是一样的。
流程说明:
1. umi
框架启动,会自动读取
models
目录下
model
文件,即
ListData.js
中的数据
2. @connect
修饰符的第一个参数,接收一个方法,该方法必须返回
{}
,将接收到
model
数据
3.
在全局的数据中,会有很多,所以需要通过
namespace
进行区分,所以通过
state[namespace]
进行获取数据
4.
拿到
model
数据中的
data
,也就是
[1, 2, 3]
数据,进行包裹
{}
后返回
5.
返回的数据,将被封装到
this.props
中,所以通过
this.props.listData
即可获取到
model
中的数据
刚刚只是将数据展现出来,如果点击按钮,需要修改
state
的值,怎么操作呢?
首先,在
model
中新增
reducers
方法,用于更新
state
中的数据:
export default {
namespace: 'list',
state: {
data: [1, 2, 3],
maxNum: 3
},
reducers : {
addNewData(state){ //state是更新前的对象
let maxNum = state.maxNum + 1;
let list = [...state.data, maxNum];
return { // 返回更新后的state对象
data : list,
maxNum : maxNum
}
}
}
}
接下来修改
List.js
新增点击事件:
import React from 'react';
import { connect } from 'dva';
const namespace = 'list';
const mapStateToProps = (state) => {
const listData = state[namespace].data;
const maxNum = state[namespace].maxNum;
return {
listData, maxNum
};
};
const mapDispatchToProps = (dispatch) => { // 定义方法,dispatch是内置函数
return { //返回的这个对象将绑定到this.props对象中
addNewData : () =>{ // 定义方法
dispatch({ // 通过调用dispatch()方法,调用model中reducers的方法
type: namespace + "/addNewData" // 指定方法,格式:namespace/方法名
});
}
}
}
@connect(mapStateToProps, mapDispatchToProps) //mapDispatchToProps:函数,将方法映射到
props中
class List extends React.Component{
render(){
return (
<div>
<ul>
{
// 遍历值
this.props.listData.map((value,index) => {
return <li key={index}>{value}</li>
})
}
</ul>
<button
onClick={()=>{this.props.addNewData()}}>
添加
</button>
</div>
);
}
}
export default List;
测试:
测试结果,和之前实现效果一样。
流程梳理如下:
三:在model中请求数据
前面我的数据是写死在
model
中的,实际开发中,更多的是需要异步加载数据,那么在
model
中如何异步加载数据 呢?
首先,创建
src
下创建
util
目录,并且创建
request.js
文件,输入如下内容:(用于异步请求数据)
// import fetch from 'dva/fetch';
function checkStatus(response) {
if (response.status >= 200 && response.status < 300) {
return response;
}
const error = new Error(response.statusText);
error.response = response;
throw error;
}
/**
* Requests a URL, returning a promise.
*
* @param {string} url The URL we want to request
* @param {object} [options] The options we want to pass to "fetch"
* @return {object} An object containing either "data" or "err"
*/
export default async function request(url, options) {
const response = await fetch(url, options);
checkStatus(response);
return await response.json();
}
然后,在
model
中新增请求方法:
import request from '../util/request';
export default {
namespace: 'list',
state: {
data: [],
maxNum: 0
},
reducers: {
addNewData(state, result) { //result就是拿到的结果数据
if(result.data){ //判断result中的data是否存在,如果存在,说明是初始化数据,直接返回
return result.data;
}
let maxNum = state.maxNum + 1;
let list = [...state.data, maxNum];
return { //更新状态值
data: list,
maxNum: maxNum
}
}
},
effects: { //新增effects配置,用于异步加载数据
*initData(params, sagaEffects) { //定义异步方法
const {call, put} = sagaEffects; //获取到call、put方法
const url = "/ds/list"; // 定义请求的url
let data = yield call(request, url); //执行请求
yield put({ // 调用reducers中的方法
type : "addNewData", //指定方法名
data : data //传递ajax回来的数据
});
}
}
}
改造页面逻辑:
import React from 'react';
import { connect } from 'dva';
const namespace = 'list';
const mapStateToProps = (state) => {
const listData = state[namespace].data;
const maxNum = state[namespace].maxNum;
return {
listData, maxNum
};
};
const mapDispatchToProps = (dispatch) => {
return {
addNewData : () =>{
dispatch({
type: namespace + "/addNewData"
});
},
initData : () => { //新增初始化方法的定义
dispatch({
type: namespace + "/initData"
});
}
}
}
@connect(mapStateToProps, mapDispatchToProps)
class List extends React.Component{
componentDidMount(){
this.props.initData(); //组件加载完后进行初始化操作
}
render(){
return (
<div>
<ul>
{
// 遍历值
this.props.listData.map((value,index) => {
return <li key={index}>{value}</li>
})
}
</ul>
<button
onClick={()=>{this.props.addNewData()}}>
添加
</button>
</div>
);
}
}
export default List;
测试:
测试结果,发现会报错,原因是返回的数据不是
json
导致,解析出错。
查看下请求:
可以看到,返回的是html代码,所以会导致出错。
完整的List.js页面代码:
import React from 'react';
import { connect } from 'dva';
const namespace = "list";
// 说明:第一个回调函数,作用:将page层和model层进行链接,返回modle中的数据
// 并且,将返回的数据,绑定到this.props
// 接收第二个函数,这个函数的作用:将定义的函数绑定到this.props中,调用model层中定义的函数
@connect((state) => {
return {
dataList : state[namespace].data,
maxNum : state[namespace].maxNum
}
}, (dispatch) => { // dispatch的作用:可以调用model层定义的函数
return { // 将返回的函数,绑定到this.props中
add : function () {
dispatch({ //通过dispatch调用modle中定义的函数,通过type属性,指定函数命名,格式:namespace/函数名
type : namespace + "/addNewData"
});
},
init : () => {
dispatch({ //通过dispatch调用modle中定义的函数,通过type属性,指定函数命名,格式:namespace/函数名
type : namespace + "/initData"
});
}
}
})
class List extends React.Component{
componentDidMount(){
//初始化的操作
this.props.init();
}
render(){
return (
<div>
<ul>
{
this.props.dataList.map((value,index)=>{
return <li key={index}>{value}</li>
})
}
</ul>
<button onClick={() => {
this.props.add();
}}>点我</button>
</div>
);
}
}
export default List;
完整的ListData.js页面代码:
import request from '../util/request';
export default {
namespace: 'list',
state: {
data: [],
maxNum: 1
},
reducers : { // 定义的一些函数
addNewData : function (state, result) { // state:指的是更新之前的状态数据, result: 请求到的数据
if(result.data){ //如果state中存在data数据,直接返回,在做初始化的操作
return result.data;
}
let maxNum = state.maxNum + 1;
let newArr = [...state.data, maxNum];
return {
data : newArr,
maxNum : maxNum
}
//通过return 返回更新后的数据
}
},
effects: { //新增effects配置,用于异步加载数据
*initData(params, sagaEffects) { //定义异步方法
const {call, put} = sagaEffects; //获取到call、put方法
const url = "/ds/list"; // 定义请求的url
let data = yield call(request, url); //执行请求
yield put({ // 调用reducers中的方法
type : "addNewData", //指定方法名
data : data //传递ajax回来的数据
});
}
}
}
完整的request.js页面代码:
// import fetch from 'dva/fetch';
function checkStatus(response) {
if (response.status >= 200 && response.status < 300) {
return response;
}
const error = new Error(response.statusText);
error.response = response;
throw error;
}
/**
* Requests a URL, returning a promise.
*
* @param {string} url The URL we want to request
* @param {object} [options] The options we want to pass to "fetch"
* @return {object} An object containing either "data" or "err"
*/
export default async function request(url, options) {
const response = await fetch(url, options);
checkStatus(response);
return await response.json();
}
四:mock数据
umi
中支持对请求的模拟,由于我们现在没有真正的服务可以返回数据,所以才需要模拟。
在项目根目录下创建
mock
目录,然后创建
MockListData.js
文件,并且输入如下内容:
export default {
'get /ds/list': function (req, res) { //模拟请求返回数据
res.json({
data: [1, 2, 3, 4],
maxNum: 4
});
}
}
进行测试:
发现,可以正常返回数据了。
页面效果也正常了
: