dva + react hooks实战Demo
前言
dva 是一款轻量级的应用框架,是阿里旗下的开源产品.dva是基于 redux 和 redux-saga 的数据流方案,然后为了简化开发体验,dva 还额外内置了 react-router 和 fetch.写过原生redux代码的同学应该体会的到,redux里面充斥着大量的样板代码,对开发者而言使用体验十分的不友好.而dva将这些繁杂的工作封装到了底层去实现,开发者只需要调用它提供的几个简单Api就能实现完全相同的功能,从而大大的提升了开发效率.
本篇博文将从实战的角度将dva和目前流行的react hooks相结合开发一款Demo应用,在实际开发中去体会dva的使用方法和其带来的变化.
需求分析
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);
登录页
说明
- 登录页需要说的点就是请求服务端数据时全局loading该如何处理.dva提供了插件机制,比如 dva-loading 可以自动处理 loading 状态.我们看下如何具体的配置.
- 安装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);
更多推荐
所有评论(0)