21 - Dva 框架使用 3 - 异步操作与模拟网络请求

基本要求

  1. 可以从首页跳转至page页面,且在page页面中包含tool和context两个组件
  2. tool实现以下三个功能
    1. 向内容列表中添加一个元素
    2. 访问静态的测试数据,并更新整个内容列表
    3. 访问动态的mock数据,并更新整个内容列表
  3. context用于展示当下最新的内容列表值

实现准备

项目主体结构基于第20号笔记

涉及概念与基础

  1. Model-effect
  2. generator函数和yield关键字

主要操作步骤

  1. 定义模拟数据文件,并将模拟数据文件尽心登记
  2. 定义ProductModel类以及需要维护的state数据
  3. 定义model中的reducer用于修改state
  4. 定义model中的effects用于接收前端发送的异步action
  5. 注册model到app
  6. 定义tool/context组件,分别实现按钮监听和内容显示
  7. 定义page组件,在其内部完成tool/context组件的集成,并于主路由尽心挂接

实现结果

无代码部分说明

基本与第20号笔记相同,不再累述

  1. 注册ProductModel到app
  2. 主路由配置ProductPage路由,并与ProductPage组件挂接
  3. 主界面事件监听,触发页面跳转事件

准备模拟请求数据

定义模拟数据文件

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(返回数据)
    }
}

其中:

  1. method: 为模拟请求的类型,一般为GET或POST
  2. url:一定要以斜杠开头的模拟网络请求地址,与method通过空格连接
  3. 返回数据可以是一个特定的数据对象,也可以是一个指定response的回调函数

模拟数据的特点

  1. dva模拟数据不是一个单独的数据集合,而是对特定http请求的模拟
  2. 需要指定请求方式,请求地址
  3. 需要指定返回的数据值
  4. 要想获得数据必须在前端发送request请求

异步Action的执行

异步action执行流程

  1. 功能组件发送一个action,且这个action被model-effect中的reducer函数接收
  2. 在model-effect-reducer中开启子线程,执行异步操作
  3. 异步执行过程中主线程不等待
  4. 异步执行完成后获得异步执行结果
  5. 将异步执行结果包装成一个新的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{

            }
        }
    }
  1. *loadStaticProductList: effect中的reducer函数名,带 * 表示表示这儿是一个generator函数
  2. 参数1 {payload}:即发送action时,所携带的参数
  3. 参数2 {call, put}: 操作句柄函数
    1. call执行由异步的方法,且返回值可以受到yield关键字限定,类似于反射调用,传入要执行的方法名和方法需要的参数
    2. put发送新的action,且被reducers中的reducer接收

语法糖带来的效果

  1. generator函数和yield关键字,让代码开看起来像是一个同步函数
  2. utils/request 只是dva框架自带的ajax请求的二次封装,使用过程中也可以根据需要创建其他的Ajax请求帮助函数,如基于Axios库

业务逻辑的进一步解耦

  1. 对于model中reducers-reducer应该理解成
    1. action的接收器
    2. 创建新state函数的调用者,而不是具体业务的执行者
    3. 新state返回函数
  2. 对于model中effects-reducer
    1. action的接收器
    2. 更新state异步函数的调用者,而不是具体异步过程执行者
    3. 向reducers-reducer发送更新消息
  3. 因此不论是reducers或者effects,如果在创建新state过程中需要执行复杂的逻辑,dva建议将这个逻辑推迟并封装到services中进行
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值