前言

dva 是一款轻量级的应用框架,是阿里旗下的开源产品.dva是基于 redux 和 redux-saga 的数据流方案,然后为了简化开发体验,dva 还额外内置了 react-router 和 fetch.写过原生redux代码的同学应该体会的到,redux里面充斥着大量的样板代码,对开发者而言使用体验十分的不友好.而dva将这些繁杂的工作封装到了底层去实现,开发者只需要调用它提供的几个简单Api就能实现完全相同的功能,从而大大的提升了开发效率.

本篇博文将从实战的角度将dva和目前流行的react hooks相结合开发一款Demo应用,在实际开发中去体会dva的使用方法和其带来的变化.

dva官方文档

Demo源代码

 

需求分析

Demo最终效果:

                                          

 

  • Demo总共有三张页面:首页,商品详情页和登录页.

  • 首页有"猜你喜欢"和"今日推荐"两个数据列表.数据从服务器端获取并渲染出来.

  • 点击列表项会获取到商品id进入到商品详情页面并展现出商品的详细信息.

  • 点击头部的登录按钮会进入登录页面,用户登陆后会持久化保存登录状态.

 

Dva概念简述

 

1.Dav项目结构描述

                                                                         

  • 按照官网文档的步骤下载完项目,它的结构如上图所示.
  • assets是静态资源文件夹,components放置一些全局组件
  • routes里面创建具体的页面.services里面编写请求后台数据接口的函数
  • utils下面的request.js是dva内部对fetch做的封装
  • index.js是应用的入口,router.js是路由文件
  • models文件夹是开发环节中最核心的部分,它里面主要编写状态处理的逻辑.以前我们编写redux的代码现在全部都放在        models里面去编写.

 

2.models文件描述

1.如果想新建一个页面,首先在routes文件下面创建HomePage编写好样式代码,随后在router.js中注册路由,如此该页面就能通过url访问了.如果我们想将页面的状态放到redux中管理,需要在models下面创建一个home.js.


import { getList } from "../services/api";
export default {
      namespace: 'home', 
      state: {
          likes:null,
          recommends:null
      },
      effects: {
        *getListHttp({ payload }, { call, put }) {  // eslint-disable-line
          const data = yield call(getList,payload);
          yield put({ type: 'getList',payload:data.data});
        },
      },
      reducers: {
        getList(state, action) {
          return { ...state, likes:action.payload.likes,recommends:action.payload.recommends };
        },
      },
      subscriptions: {
       keyEvent({dispatch,history}) {
         
       }
     }  
};

2.这个模型文件最终导出来的一个对象.namespace是命名空间,页面的组件如果想要获取此model中的state和函数需要通过namespace来获取.

  •  页面上获取数据状态

  • 页面上调用action修改state.只要在页面上用connect包裹的组件,props里面就会拥有dispatch方法.dispatch可以调用model中的同步和异步的方法.type是必传的参数,home是命名空间,getListHttp是model中定义的函数.payload是用户自定义传递的可选参数.只要调用了dispatch方法,对应的model中相应的函数就会被调用.

3.我们拉回来继续分析models中的数据结构.state不言而喻是定义的数据状态,reducers里面定义的是操作redux中state的同步方法.在reducers里面的每个函数中,第一个参数state是数据状态,第二个参数action里面包含了用户在页面上调用dispatch函数时传递过来的参数.我们最终要返回一个新的state回去才能改变redux中的state.

4.effects里面盛放的也是函数,但是呢它和reducers不同.reducers里面装的是同步函数,effects里面放的是异步函数(比如请求服务器端数据的操作).

  • effects中定义的函数的第一个参数还是用户在页面上执行disptach方法传递过来的action,action里面可能含有用户的自定义参数payload.

  • 函数的第二个参数call和put的作用分别是什么呢?call是用来调用异步方法,比如上面的代码中getList就是一个请求服务器端数据的异步函数.使用call来调用它请求服务器端的数据,用yield将线程阻塞在那里,等到服务器返回数据了再往下执行代码.

  • 现在服务器端已经返回数据了,我现在要对这些数据做下处理存储到redux中去.此时就可以使用put来调用reducers中的同步函数来操作redux中的状态.

5. subscriptions看自己的需求来进行配置,它不是必须的.它里面定义的函数如果监听到页面上用dispatch派发了action,那么这些函数都会执行,但是呢只会执行一次.在实际应用场景中,可以在subscriptions里面的函数利用其参数history可以编写一些监听路由变化的方法.

 

首页开发

 

                                                    

说明

通过上面对dva基本概念的讲述我们可以来了解一下页面组件如何与models进行通信的.

  • 从dva中导出connect并包裹页面组件.这样就可以使用dispatch来派发action来触发model中定义的同步或者异步函数.
  • 编写mapStateToProps函数放置到connect中,页面组件就能从props获取到redux的数据来渲染页面.


首页源码

import React,{useEffect} from 'react';
import { connect } from 'dva';
import './style.scss';
import Header from "../../components/Header";
import Product from "./components/Product";

function HomePage(props) {
  
  useEffect(()=>{
    const {dispatch} = props;
    dispatch({
      type:"home/getListHttp",
      payload:{}
    })
  },[])

  const { likes = null,recommends = null } = props;

  if(!likes || !recommends){
    return null;
  }

  return (
    <div className="home-page">
      <Header title="首页"/>
      <Product data={likes}/>
      <Product data={recommends}/>
    </div>
  );
}

const mapStateToProps = (state)=>{
  return {
    likes:state.home.likes,
    recommends:state.home.recommends
  }
}

export default connect(mapStateToProps)(HomePage);

 

首页Header组件源码

 

说明

  • 了解Header组件的目的是为了知道在dva中子组件如何使用路由跳转.
  • 从dva中引入withRouter包裹组件,这样就可以使用props.history里面的api实现跳转

 

源码

import React from 'react';
import "./style.scss";
import { withRouter } from 'dva/router'

const Header = (props)=>{

    const {title,has_back=false,has_login=true} = props;

    function goBack(){
        props.history.goBack();
    }

    function login(){
        props.history.push("/login");
    }

    return (
        <div className="header">
              {has_back?<a className="back" onClick={goBack}>返回</a>:null} 
              {title}
              {has_login?<a className="login" onClick={login}>登录</a>:null}
        </div>
    );
}

export default withRouter(Header);

 

 

详情页

                                                          

 

说明

  • 如果了解了首页的开发规律,其他页面也都差不多.
  • 我们这里着重看一下dva中如何获取路由参数.在props.match.props里面获取参数.

 

源码

import React,{useEffect} from 'react';
import { connect } from 'dva';
import './style.scss';
import Header from "../../components/Header";

function DetailPage(props) {

  const { dispatch,match,product_data } = props;

  useEffect(()=>{
    const { id } = match.params;
    dispatch({
      type:"detail/getDataHttp",
      payload:{
        id
      }
    })
  },[])

  if(!product_data){
    return null;
  }

  return (
    <div className="detail-page">
      <Header title="详情页" has_back={true}/>   
      <div className="item">
        <p className="item_top box">
          <i className="lt">id</i>
          <i className="gt">{product_data.id}</i>
        </p>
      </div>
      <div className="item">
        <p className="item_top box">
          <i className="lt">名称</i>
          <i className="gt">{product_data.name}</i>
        </p>
      </div>
      <div className="item">
        <p className="item_top box">
          <i className="lt">数量</i>
          <i className="gt">{product_data.count}</i>
        </p>
      </div>
      <div className="item">
        <p className="item_top box">
          <i className="lt">价格</i>
          <i className="gt">{product_data.price}</i>
        </p>
      </div>
      <div className="item high">
        <p className="item_top box">
          <i className="lt">照片</i>
          <i className="gt"><img src={product_data.img}/></i>
        </p>
      </div>
      <div className="item high">
        <p className="item_top box">
          <i className="lt">描述</i>
          <i className="gt">{product_data.desc}</i>
        </p>
      </div>
    </div>
  );
}

const mapStateToProps = (state)=>{
  return {
    product_data:state.detail.product_data
  }
}

export default connect(mapStateToProps)(DetailPage);

 

登录页

                                  

                                              

说明

  1. 登录页需要说的点就是请求服务端数据时全局loading该如何处理.dva提供了插件机制,比如 dva-loading 可以自动处理 loading 状态.我们看下如何具体的配置.
  2. 安装dva-loading并在入口文件中启用插件.

                                 

    3.此时在页面的组件中就可以直接通过state.loading获取加载的状态了.在这背后,dva会自动监听是否有异步的ajax请求,根据它的请求状态(请求中还是请求完成)dva会自动处理全局的loading数据,我们只需要获取它就可以了.

                   

   4.获取到loadin后通过查看它的属性global是为true还是false来决定是否渲染加载中的图标.

           

源码

import React,{useEffect} from 'react';
import { connect } from 'dva';
import './style.scss';
import Header from "../../components/Header";
import LoginForm from "./components/LoginForm";
import LoginState from "./components/LoginState";
import Loading from "../../components/Loading";

function LoginPage(props) {

  const { dispatch,user,loading } = props;

  useEffect(()=>{
    dispatch({
      type:"login/getUser"
    })
  },[])

  return (
    <div className="login-page">
      <Header title="登录页" has_back={true} has_login={false}/>
      {
        user?<LoginState/>:<LoginForm/>
      }
      {loading.global?<Loading/>:false}
    </div>
  );
}

const mapStateToProps = (state)=>{
  return {
    user:state.login.user,
    loading:state.loading
  }
}

export default connect(mapStateToProps)(LoginPage);


 

 

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐