dva 是一款轻量级的应用框架,是阿里旗下的开源产品.dva是基于 redux 和 redux-saga 的数据流方案,然后为了简化开发体验,dva 还额外内置了 react-router 和 fetch.写过原生redux代码的同学应该体会的到,redux里面充斥着大量的样板代码,对开发者而言使用体验十分的不友好.而dva将这些繁杂的工作封装到了底层去实现,开发者只需要调用它提供的几个简单Api就能实现完全相同的功能,从而大大的提升了开发效率.
本篇博文将从实战的角度将dva和目前流行的react hooks相结合开发一款Demo应用,在实际开发中去体会dva的使用方法和其带来的变化.
dva官方文档
Demo源代码
Demo最终效果:
Demo总共有三张页面:首页,商品详情页和登录页.
首页有"猜你喜欢"和"今日推荐"两个数据列表.数据从服务器端获取并渲染出来.
点击列表项会获取到商品id进入到商品详情页面并展现出商品的详细信息.
点击头部的登录按钮会进入登录页面,用户登陆后会持久化保存登录状态.
models文件夹是开发环节中最核心的部分,它里面主要编写状态处理的逻辑.以前我们编写redux的代码现在全部都放在 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进行通信的.
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 (
);
}
const mapStateToProps = (state)=>{
return {
likes:state.home.likes,
recommends:state.home.recommends
}
}
export default connect(mapStateToProps)(HomePage);
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 (
);
}
export default withRouter(Header);
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 (
id
{product_data.id}
名称
{product_data.name}
数量
{product_data.count}
价格
{product_data.price}
照片
描述
{product_data.desc}
);
}
const mapStateToProps = (state)=>{
return {
product_data:state.detail.product_data
}
}
export default connect(mapStateToProps)(DetailPage);
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 (
{
user? :
}
{loading.global? :false}
);
}
const mapStateToProps = (state)=>{
return {
user:state.login.user,
loading:state.loading
}
}
export default connect(mapStateToProps)(LoginPage);