文章目录
基本要求
- 可以从首页跳转至page页面,且在page页面中包含tool和context两个组件
- tool实现以下三个功能
- 向内容列表中添加一个元素
- 访问静态的测试数据,并更新整个内容列表
- 访问动态的mock数据,并更新整个内容列表
- context用于展示当下最新的内容列表值
实现准备
项目主体结构基于第20号笔记
涉及概念与基础
- Model-effect
- generator函数和yield关键字
主要操作步骤
- 定义模拟数据文件,并将模拟数据文件尽心登记
- 定义ProductModel类以及需要维护的state数据
- 定义model中的reducer用于修改state
- 定义model中的effects用于接收前端发送的异步action
- 注册model到app
- 定义tool/context组件,分别实现按钮监听和内容显示
- 定义page组件,在其内部完成tool/context组件的集成,并于主路由尽心挂接
实现结果
无代码部分说明
基本与第20号笔记相同,不再累述
- 注册ProductModel到app
- 主路由配置ProductPage路由,并与ProductPage组件挂接
- 主界面事件监听,触发页面跳转事件
准备模拟请求数据
定义模拟数据文件
mock\productMockData.js
import Mock from "mockjs"
export default {
// 要以斜杠号开头
"GET /api/productStaticData" : {
status : true,
msg: "",
data : [
{ name : "name1"} ,
{ name : "name2"} ,
{ name : "name3"} ,
{ name : "name4"} ,
{ name : "name5"} ,
]},
//这种写法,本质上还是静态数据,只是静态数据是由mock动态生成的
//即是说,mock执行一次后则不在执行
//具体的mock函数用法,参考mockjs相关文档
"GET /api/productMockData2" : Mock.mock({
'status': true,
'msg': "",
'data|10-25' : [{
name : "@cname"
}]
}),
//这种写法,每次都执行了回调函数,因此每一次请求,都会生成不同的数据
"GET /api/productMockData" : (req, res) => {
res.status(200).json(Mock.mock({
'status': true,
'msg': "",
'data|10-25' : [{
name : "@cname"
}]
}))
}
}
注册模拟数据文件到模拟数据容器中
.roadhogrc.mock.js
import * as productData from "./mock/productMockData"
export default {
...productData
};
定义ProdcutModel
src\models\productModel.js
import request from "../utils/request"
import * as serviceApi from "../services/productService"
export default {
namespace: "porductModel",
state: {
dataSource: [
{ name: "defalut" }
]
},
reducers: {
//向state的dataSouce列表中添加一个元素
addProduct(state, action) {
let newState = {...state} //注意深拷贝
newState.dataSource.push(action.payload)
return newState
},
//整个更新state
reBuildProductList(state, action) {
return { dataSource: action.payload }
}
},
effects:
{
//异步形式接收传来的action
*addProductAsync({ payload }, { call, put }) {
//数据更新过程通过put函数,推迟到reducers的addProduct方法执行
yield put({type:"addProduct", payload});
},
//加载静态模拟数据
*loadStaticProductList({ payload }, { call, put }) {
//直接调用utils包中的工具函数request,获得响应的response对象
//再从response中获得返回数据体
//注意异步函数调用方法,利用call函数实现类似反射调用request
const response = yield call(request, "/api/productStaticData")
if (response.data) {
const datalist = response.data.data
//state的修改,推迟到reducers中进行
yield put({ type: "reBuildProductList", payload: datalist })
}
//异常处理
else{
console.error(response.err)
}
},
//加载mock生成的动态数据
*loadMockProductList({ payload }, { call, put }) {
//假如异步请求过程中还存在其他业务逻辑,则可以将逻辑推迟到serviceAPI中进行
const response = yield call(serviceApi.loadmockdata)
if (response.data) {
const datalist = response.data.data
//state的修改,推迟到reducers中进行
yield put({ type: "reBuildProductList", payload: datalist })
}
//异常处理
else{
console.error(response.err)
}
}
}
}
src\services\productService.js
import request from '../utils/request';
export function loadmockdata() {
//此处可以进行其他业务逻辑处理
return request('/api/productMockData');
}
ProductPage组件
src\routes\ProductPage\ProductPage.jsx
import React from 'react'
import ProductContext from '../../components/ProductContext/ProductContext'
import ProductTool from '../../components/ProductTool/ProductTool'
export default function ProductPage() {
return (
<div>
<ProductTool></ProductTool>
<ProductContext></ProductContext>
</div>
)
}
ProductTool组件
src\components\ProductTool\ProductTool.jsx
import { Button } from 'antd'
import { connect } from 'dva'
import React, { Fragment } from 'react'
import * as actions from "../../action/ProductModelAction"
function ProductTool(props) {
//发送添加事件
const onBtnAppendProductItemClick = () => {
props.dispatch(actions.addProductAsync({name : Date.now().toString()}))
}
//发送加载静态列表事件
const onBtnLoadStaticListClick = () => {
props.dispatch(actions.loadStaticProductList())
}
//发送添加动态模拟列表事件
const onBtnLoadMockListClick = () => {
props.dispatch(actions.loadMockProductList())
}
return (
<Fragment>
<Button.Group>
<Button type = "primary" onClick = {onBtnAppendProductItemClick}>append product item</Button>
<Button type = "default" onClick = {onBtnLoadStaticListClick}>load static list</Button>
<Button type = "dashed" onClick = {onBtnLoadMockListClick}>load mock list</Button>
</Button.Group>
</Fragment>
)
}
export default connect()(ProductTool)
ProductContext组件
src\components\ProductContext\ProductContext.jsx
import { connect } from 'dva'
import React from 'react'
function ProductContext({data}) {
return (
<ul>
{data.dataSource.map((value,index)=>{
return <li key = {index}>{`name : ${value.name}`}</li>
})}
</ul>
)
}
//从redux空间中,提取出productModel的数据,并名为data,传入当前组件的props属性中
const maps = (reduxState) => {
return { data : reduxState.porductModel}
}
export default connect(maps)(ProductContext)
应用总结
Dva中的模拟数据容器
模拟数据文件基本格式要求如下:
export default {
//返回静态数据
"[method] /url" : { 返回数据 },
//返回特定的response对象,
"[method] /url" : (req,res)=> {
res.status(200).json(返回数据)
}
}
其中:
- method: 为模拟请求的类型,一般为GET或POST
- url:一定要以斜杠开头的模拟网络请求地址,与method通过空格连接
- 返回数据可以是一个特定的数据对象,也可以是一个指定response的回调函数
模拟数据的特点
- dva模拟数据不是一个单独的数据集合,而是对特定http请求的模拟
- 需要指定请求方式,请求地址
- 需要指定返回的数据值
- 要想获得数据必须在前端发送request请求
异步Action的执行
异步action执行流程
- 功能组件发送一个action,且这个action被model-effect中的reducer函数接收
- 在model-effect-reducer中开启子线程,执行异步操作
- 异步执行过程中主线程不等待
- 异步执行完成后获得异步执行结果
- 将异步执行结果包装成一个新的action,发送给model-reducers中的reducer函数,以进行model-state修改
effect中reducer函数的说明
effects:
{
*loadStaticProductList({ payload }, { call, put }) {
//直接调用工具函数request,获得响应的response对象
//再从response中获得返回数据体
//注意异步函数调用方法
const response = yield call(request, "/api/productStaticData")
if (response.data) {
const datalist = response.data.data
//state的修改,推迟到reducers中进行
yield put({ type: "reBuildProductList", payload: datalist })
}
//异常处理
else{
}
}
}
- *loadStaticProductList: effect中的reducer函数名,带 * 表示表示这儿是一个generator函数
- 参数1 {payload}:即发送action时,所携带的参数
- 参数2 {call, put}: 操作句柄函数
- call执行由异步的方法,且返回值可以受到yield关键字限定,类似于反射调用,传入要执行的方法名和方法需要的参数
- put发送新的action,且被reducers中的reducer接收
语法糖带来的效果
- generator函数和yield关键字,让代码开看起来像是一个同步函数
- utils/request 只是dva框架自带的ajax请求的二次封装,使用过程中也可以根据需要创建其他的Ajax请求帮助函数,如基于Axios库
业务逻辑的进一步解耦
- 对于model中reducers-reducer应该理解成
- action的接收器
- 创建新state函数的调用者,而不是具体业务的执行者
- 新state返回函数
- 对于model中effects-reducer
- action的接收器
- 更新state异步函数的调用者,而不是具体异步过程执行者
- 向reducers-reducer发送更新消息
- 因此不论是reducers或者effects,如果在创建新state过程中需要执行复杂的逻辑,dva建议将这个逻辑推迟并封装到services中进行