阅读本文章需有以下基础
1.react
2.dva
3.umi
本文主要是使用umi+dva完成仓库管理,model文件的结构、写法,connect怎么把model的数据和组件关联,数据之间如何传递修改等问题。
(本人也在学习中,以下内容经供参考,可能存在一些问题,欢迎指正)
一、dva是基于redux、redux-saga和react-router的轻量级前端框架
我简单的理解为redux用于处理仓库的同步数据,redux-sage用于处理仓库的异步数据,react-router用于页面跳转。dva官网https://dvajs.com/guide/
二、umi干嘛用的
帮我们省去很多麻烦事的框架。官网https://umijs.org/zh-CN/docs
**三、 目录的组织结构
在umi搭建的项目中的 约定式的 model 组织方式如上,所以先创建一个umi项目(不会的可以看官网),然后在pages文件下创建以下几个文件(一般项目上都会有这几个文件,这里的组件表示的是子组件,这里所说页面其实也是一个组件)
四、model里有些啥?
model.js文件里的内容如下,model里其实就是一个对象,我们把这个对象导出
export default {
//namespace命名空间,相当于给model取个名字,但是各个model的namespce是不能重复的
namespace: 'test',
//state我理解为是数据仓库,就是存数据的地方,model里的数据都是存放在这里的
state : {
name: 'wang'
},
/*reducers把数据存到仓库(存到state)里的唯一方法,我们修改state里的数据不能直接像this.name='liu'这样去修改,而必须通过调用reducers里的方法,在之后会详细讲到*/
reducers:{
},
/*异步方法,简单来说我们的异步请求就写在这里*/
effects: {
},
/*订阅,在这里我的理解就是监听页面的,比如监听到进入了某某页面就让它执行相关代码之类的*/
subscriptions: {
}
}
model的结构就是这个样子,但是怎么把model和我们写的组件关联起来呢,只需要用到connect就可以啦。首先从dva中导入connect,然后把Index包裹起来。
index.js文件里的内容如下
import React from 'react';
import { connect } from 'dva'
class Index extends React.Component {
render() {
return (
<div>
<div className='box'>
</div>
</div>
);
}
}
//通过connect把model和我们写的Index关联起来,之后会解释mapStateToProps,先这样写,test是model的命名空间
const mapStateToProps = ({test}) => {
return {
...test
}
}
export default connect(mapStateToProps)(Index);
简单解释一下这个connect,connect是一个高阶函数,高阶函数就是说把一个函数作为另一个函数的参数。connect接受函数mapStateToProps作为参数,connect(mapStateToProps)返回的也是一个函数,然后再把Index作为这个返回的函数的参数。
五、如何进行数据传递的?
先看一张图
简单理解一下就是我们的页面或者订阅通过dispatch去调用reducer和effect里的函数,在effect里面去实现services请求,把数据通过reducer放到state里面(reducer操作state里的数据的唯一途径),最后再通过connect吧state和页面关联起来,我们就可以在页面中拿到model里的数据。在页面中调用接口dispatch-》effect里面的方法,在页面中修改state的里的数据dispatch-》reducer里面的方法。
**
六、connect详解
之前说到connect把model和页面链接起来,为了方便讲解,现在model.js reducers里面先写一个save方法。
reducers:{
save(state,action){
//必须return,否则会报错
return {
name: '111'
}
}
},
回到Index.js页面,我们再来说connect。
connect(mapStateToProps, mapDispatchToProps,mergeProps,options)
connect接收四个参数
mapStateToProps允许我们将 store 中的数据作为 props 绑定到组件上。
mapDispatchToProps将action作为props绑定到组件上。
mergeProps和options基本不传,我们只要掌握前两个就好了
mapStateToProps(state,ownProps){ retunrn obj } 必须返回一个对象
不妨打印一下看看两个参数是什么东西,以及返回的对象会到哪里去
Index.js内容如下
import React from 'react';
import { connect } from 'dva'
class Index extends React.Component {
componentDidMount() {
console.log('=========props');
console.log(this.props);
}
render() {
return (
<div>
<div>
<div>姓名:{this.props.name}</div>
</div>
</div>
);
}
}
const mapStateToProps = (state, ownProps) => {
console.log('mapStateToProps=========');
console.log('state',state);
console.log('ownProps',ownProps);
return {
hobby: '打游戏'
}
}
export default connect(mapStateToProps)(Index);
这就很明显了,我们在mapStateToProps 里返回什么,组件的props就会接收到什么,然而第一个参数里我们可以取到model里state的值,所以我们只需要把state里的值返回,这样我们就可以在组件中得到model的值。
const mapStateToProps = ({test}) => {
return {...test}
}
mapDispatchToProps可以是一个对象也可以是一个函数
mapDispatchToProps(dispatch,ownProps),ownProps与上面的一样就不多加赘述
注意:对象的属性值需是函数
//对象写法
// const mapDispatchToProps = {
// add: () => ({type: 'test/save'}),
// }
//函数写法
const mapDispatchToProps = (dispatch) => ({
save() {
dispatch({type: 'test/save'})
},
})
export default connect(mapStateToProps,mapDispatchToProps)(Index);
返回的函数也会被props接收,这样我们就可以在props里取到该函数并调用它。
注意:如果传了第二个参数mapDispatchToProps,那么props里就没有dispatch了,也就是说不能通过this.props.dispatch({type: ‘test/save’)这样去派发它,只能通过调用mapDispatchToProps返回的函数this.props.save()进行派发。
**
七、Reducer
//格式如下
reducers:{
save(state,action){
//必须return,否则会报错
return {
name: '111'
}
}
},
页面组件与model关联以后我们是这样去使用它的。
this.props.dispatch({type: 'test/save',payload: {msg: '你好呀'}})
‘上次的state’做个解释,就是说如果我连续dispatch了两次reducer,第二次的参数state就是第一次的dispatch之后返回的state,也就是说第一次reducer以后state的值变了,第二次拿到的state就是改变后的state.
我们一般把需要传递的数据都放在payload里面,type的值一般是用不到的,所以一般我们会这样写。
reducers:{
save(state,{payload}){
return {...state, ...payload}
}
},
八、subscriptions
subscriptions: {
test({history,dispatch}){
console.log('history',history);
console.log('dispatch',dispatch);
}
}
在subscriptions里随便写一个方法,打印出来看看。by the way,subscriptions里的方法,随便进入一个页面则都会执行,不需要与model进行关联才会调用,比如我现在是写这个目录下的,我随便进入一个其他的页面依旧打印。
有人可能觉得,那事件监听就事件监听被,干嘛要单独抽出来放在model里面弄成个订阅。如果你需要用到一些数据,并且处理数据后的逻辑仅与当前model相关,那么就应该用 subscriptions。这么一想是不是,我们就可以在subscription里方便的操作model里面的数据了呀。
subscriptions: {
test({history,dispatch}){
history.listen((location) => {
if(location.pathname === '/reactRouter/路由起步/user'){
dispatch({type: 'save',payload:{age: '18'}}) //在model里面dispatch就不需要加命名空间哦
}
})
}
}
九、effects
格式为*func(action,effects),action和之前的一样,effects里有很多方法,只要会用call和put就够了。
我们在effects里调用接口,我这里只做个模拟,在service.js里模拟一个接口,导出它,因为请求接口是异步的所以在这里使用async
好了,现在我们就可以在componentdidmount里面进行派发调用这个接口,然后把接口返回的数据存到state里,再把state里的数据显示到页面上啦。
页面显示