什么是dva
dva 首先是一个基于 redux 和 redux-saga 的数据流方案,然后为了简化开发体验,dva 还额外内置了
react-router 和 fetch,所以也可以理解为一个轻量级的应用框架。 ——(来自官网的介绍)
快速上手
通过 npm 安装 dva-cli 并确保版本是 0.9.1 或以上。
$ npm install dva-cli -g
$ dva -v
dva-cli version 0.9.1
创建新应用
$ dva new dva-quickstart
然后我们cd到项目目录,运行
$ cd dva-quickstart
$ npm start
这时我们便创建并运行起来了。
引入antd
使用npm安装antd
$ npm install antd babel-plugin-import --save
编辑 .webpackrc
,使 babel-plugin-import 插件生效。
{
+ "extraBabelPlugins": [
+ ["import", { "libraryName": "antd", "libraryDirectory": "es", "style": "css" }]
+ ]
}
到此,我们的准备工作完成😊
定义路由
打开src目录,我们可以看到routes的目录,这里放置着我们需要挂载到router上的页面(page),这里dva已经给我们创建了一个叫做IndexPage的页面,我们再创建一个新的页面(ProductPage),然后再打开src下的router文件,在这里我们可以配置所有的挂载到路由上的页面。
创建新的Component组件
我们在src下的components文件夹下新建一个Products的jsx文件,然后写一个Products的函数式组件(类式组件也行,在这里使用了函数式组件)。
import React from "react";
function Products(){
return (
<div>products</div>
)
}
export default Products;
然后在之前的ProductsPage页面中引入。
import Products from '../../components/Products';
const ProductsPage = () => {
return (
<div>
<Products/>
</div>
);
};
// export default Products;
export default ProductsPage;
Model创建
在src下,我们可以看见一个models的文件夹,在这里面我们新建一个products.js文件。
照着给的示例,我们在products中写一些东西。
export default {
namespace: 'products',
state: {
productsList:[
{name:'大豆'},
{name:'小米'}
]
},
};
namespace:命名空间,标识我们这个model的名字。state存储数据。当然这里的state的初始话数据我们还可以放在src下的index文件中。
接下来,我们继续完善一下model
reducers:{
updateList(state, action){ //此处的state不和model中的state严格使用一样的名字
let curentList = deepClone(state);
curentList.productsList.push(action.payload); //此处的payload和dispatch中传入的相对应,名字一样
return curentList;
}
},
};
function deepClone(value){
return JSON.parse(JSON.stringify(value));
}
再在ProductsPage中连接model
import Products from '../../components/Products';
import { connect } from 'dva';
const ProductsPage = (props) => {
console.log(props);
return (
<div>
<Products dispatch={props.dispatch} productsList={props.productsList}/>
</div>
);
};
const mapStateToProps = (state)=>{
return {
productsList:state.products //products是命名空间的名字
}
}
// export default Products;
export default connect(mapStateToProps)(ProductsPage);
connect是dva提供的用来连接model的高阶函数。
上面ProductsPage中向组件Products传了参数,在Products子组件中接收。
import React from "react";
function Products(props){
const {productsList} = props.productsList;
const addProducts=()=>{
const curProduct = {name:'玉米'};
props.dispatch({
type:'products/updateList', //此处为model中的namespace+reducers中的方法的名字
payload:curProduct //此处的payload和model中的相对应
})
}
return (
<div>products
<ul>
{productsList.map((item,index)=>{
return <li key={index}>{item.name}</li>
})}
</ul>
<button onClick={addProducts}>添加商品</button>
</div>
)
}
export default Products;
路由跳转
- Link事件跳转
import {Link} from "dva/router"
//.......
<Link to="/">去首页</Link>
- 点击事件
利用props上的history(只有被Router包裹的才有history属性)
<Router history={history}>
<Switch>
<Route path="/" exact component={IndexPage} />
<Route path="/products" component={ProductsPage}/>
</Switch>
</Router>
所以在这里我们的子组件Products没有history属性,这时我们就需要从父组件中传参到子组件。
//父组件传递
<Products history={props.history} dispatch={props.dispatch} productsList={props.productsList}/>
//子组件接收
const gotoHome = ()=>{
props.history.push('/');
}
//......
<button onClick={gotoHome}>去首页</button>
利用原生路由提供的withRouter(dva把原生的代码直接拷过来了,dva中也有此提供)。
withRouter是一个高阶函数
import {withRouter,Link} from "dva/router"
const gotoHome = ()=>{
props.history.push('/');
}
<button onClick={gotoHome}>去首页</button>
export default withRouter(Products);
- routerRedux方案
import {withRouter,Link,routerRedux} from "dva/router"
const gotoHomeRedux = ()=>{
props.dispatch(routerRedux.push('/'))
}
<button onClick={gotoHomeRedux}>去首页Redux</button>
model异步处理
比起model的同步操作,异步操作是在effects中进行处理之后再传递一个action到reducers中请求相关操作完成state的更新。下面让让我们来看一个简单的例子
const addProductsAnysc = ()=>{
const curProductAnysc = {name:'异步玉米'}
props.dispatch({
type:'products/updateListAnysc',
payload:curProductAnysc
})
}
<button onClick={addProductsAnysc}>异步添加商品</button>
//---------------------------------------------------------------
reducers:{
updateList(state, action){
console.log('state',state);
let curentList = deepClone(state);
curentList.productsList.push(action.payload);
return curentList;
}
},
effects:{
*updateListAnysc({payload},{call,put}){
yield put({
type:'updateList',
payload
})
}
}
如上展示的代码,我们先在子组件中创建一个点击的按钮用来触发事件,该写法和原来没有什么区别,唯一改变的是调用的effects中的方法,再通过effects中的方法调用了reducers中的方法更新state。
关于dva中的mock使用
- 在根目录下的mock文件中新建一个product.js文件
module.exports = {
"GET /api/product":{"name":"高粱"}
}
- 在根目录下的
.roadhogrc.mock.js
中添加
export default {
...require("./mock/product.js")
};
- 最后在src下的service中的example文件中添加如下
export function getProduct(){
return request('/api/product')
}
可以更改下src中utils下的request.js文件(新增了post)
import fetch from 'dva/fetch';
import qs from 'querystring';
function parseJSON(response) {
return response.json();
}
function checkStatus(response) {
if (response.status >= 200 && response.status < 300) {
return response;
}
const error = new Error(response.statusText);
error.response = response;
throw error;
}
function handleHeaders(options) {
const headers = options.headers = options.headers ? options.headers : {};
const defaultHeaders = {
'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
};
options.headers = Object.assign({}, defaultHeaders, headers);
if (options.method === 'post') {
var body = options.body ? options.body : {};
body = qs.stringify(body);
options.body = body;
}
}
/**
1. Requests a URL, returning a promise.
2. 3. @param {string} url The URL we want to request
4. @param {object} [options] The options we want to pass to "fetch"
5. @return {object} An object containing either "data" or "err"
*/
export default function request(url, options = {}) {
//get
if (!options.method) {
url += `?${qs.stringify(options.params)}`;
}
//处理头部
handleHeaders(options);
return fetch(url, options)
.then(checkStatus)
.then(parseJSON)
.then(data => ({ data }))
.catch(err => ({ err }));
}
上面这些是我们自己自定义的数据,如果我们想使用mock提供的数据呢?
- 安装mock
npm install mockjs
- 在mock文件夹下新增posts文件
const Mock = require("mockjs")
module.exports = {
'GET /api/posts': (req, res) => {
res.status(200).json({
users: Mock.mock({
'data|80-100': [
{
id: '@id',
name: '@name',
nickName: '@last',
phone: /^1[34578]\d{9}$/,
'age|11-99': 1,
address: '@county(true)',
isMale: '@boolean',
email: '@email',
createTime: '@datetime',
avatar() {
return Mock.Random.image('100x100', Mock.Random.color(), '#757575', 'png', this.nickName.substr(0, 1))
},
},
],
})
})
},
}
- 配置service
export function posts(){
return request('/api/posts');
}
dva中调用mock提供的数据
先是子组件中
const addProductsHttp = ()=>{
props.dispatch({
type:'products/updateListHttp',
payload:101
})
}
<button onClick={addProductsHttp}>添加高粱</button>
再是model里面
reducers:{
updateList(state, action){
console.log('state',state);
let curentList = deepClone(state);
curentList.productsList.push(action.payload);
return curentList;
}
},
effects:{
*updateListAnysc({payload},{call,put}){
yield put({
type:'updateList',
payload
})
},
*updateListHttp({payload}, {call,put}){
const result = yield call(api.getProduct,payload)
console.log('payload',payload);
const data = result.data;
if(data){
yield put({
type:'updateList',
payload:data
})
}
}
}
结语:在这里也只是很浅显的学习了一下dva,参考了b站老师的视频
https://www.bilibili.com/video/BV1R441137as?p=1
如果文章有不对的地方,欢迎大家指正。