🐤本篇文章是『从零玩转 TypeScript + React 项目实战』系列文章的第 4 篇,主要介绍『Dva』管理数据
通过上一篇文章的学习,我们已经知道了『Dva』是什么,以及『Dva』的使用方式,如何使用『Dva』来渲染我们的组件,其实 dva 的主用作用并不是用来渲染组件的,它的主要作用是对 redux、redux-saga 进行封装,它的作用就是用来管理数据的,那么我们就来看一下『Dva』是如何管理数据的。
那么如何使用『Dva』来管理数据呢?要想使用『Dva』来管理数据,我们需要先了解一下『Dva』的核心,『Dva』的核心有三个,分别是:
model
,也就是说我们可以给每一个组件定义一个 model,然后在这个 model 中就可以保存对应组件的数据,就可以保存对应组件的 reducer、就可以保存对应组件的负作用,就可以保存对应组件订阅的内容:所以说知道了这个 model 之后要想使用 dva 来管理数据,那么我们就需要先定义一个 model。
那么如何定义一个 model 呢?我先不管三七二十一,我先定义一个 Home 组件:
/* index.js */
+ function Home() {
+ return (
+ <div>
+ Home
+ </div>
+ )
+ }
接下来我的要求是定义一个 model,用这个 model 来保存 Home 组件的数据,来保存 Home 组件的 reducer、来保存 Home 组件的负作用、来保存 Home 组件订阅的内容,那么如何定义呢?
指定命名空间
model 其实就是一个特殊的对象,那特殊在哪里呢?特殊在他有一些特殊的 key,比如说它里面,有一个叫做 namespace 的这么一个 key,通过这个 key,我们可以指定对应 model 的命名空间,这里我定义一个叫 homeModel 的 model,然后我在这个 model 中通过 namespace 来指定这个 model 的命名空间是 home
:
/* index.js */
+ let homeModel = {
+ namespace: 'home'
+ }
为什么要指定命名空间呢?因为将来我们是可以定义多个 model 的,例如这里我在新增一个 model 叫做 aboutModel,然后我在这个 model 中通过 namespace 来指定这个 model 的命名空间是 about
:
/* index.js */
+ let aboutModel = {
+ namespace: 'about'
+ }
将来呢,我们就可以通过命名空间来区分当前使用的到底是哪一个 model,反正你只需要知道 model 就是一个特殊的对象,这个对象里面有一个特殊的 key,叫做 namespace,通过这个 key,我们可以指定对应 model 的命名空间,命名空间的作用就是用来区分不同的 model。
保存数据
那么接下来呢,我们就可以在这个 model 中保存数据了,那么如何保存数据呢?在这个特殊的对象中,还有一个特殊的 key,叫做 state,通过这个 key,我们可以保存对应 model 的数据,例如我在 homeModel 中定义一个 state,然后我在这个 state 中定义一个 count,这个 count 的值是 0:
/* index.js */
let homeModel = {
namespace: 'home',
+ state: {
+ count: 0
+ },
}
如果说这里要写上一段注释信息的话,那么就是:指定当前命名空间保存的数据。
处理数据
保存完毕数据之后我们还要干嘛?是不是要处理数据,这里可以通过 reducer 来处理,所以说这里我就要给当前的 model 定义一个 reducer,那么如何定义呢?在这个特殊的对象中,还有一个特殊的 key,叫做 reducers,通过这个 key,我们可以定义对应 model 的 reducer,例如我在 homeModel 中定义一个 reducer:
/* index.js */
let homeModel = {
namespace: 'home',
state: {
count: 0
},
+ reducers: {
+ }
}
reducers 定义好了,那么 reducers 中是不是要定义一些处理方法,比如说我在这里定义一个叫做 add 的处理方法,这个方法对应一个函数,这个函数接收两个参数,第一个参数是 state,第二个参数是 action,state 是过去的状态,也就是过去的值,action 是当前的动作,也就是当前派发的动作,和过去中的 reducer 是一样的。
/* index.js */
let homeModel = {
namespace: 'home',
state: {
count: 0
},
reducers: {
+ add(state, action) {
+ }
}
}
那么 reducer 中的 key 有什么作用呢?reducer 中 key 的作用,其实是用来指定当前 reducer 的处理方法的,也就是类型,例如我上面定义的一个 key 叫做 add,代表着将来别人派发 type 是 add playload 是什么 xx 的时候 {type: add, playload: xxx}
,这个时候就会去 reducers 中找到 key 是 add 的这个哥们来去执行。
再比如说,别人派发一个 sub,这个时候就会去 reducers 中找到 key 是 sub 的这个哥们来去执行,发现没有,那么就不会执行任何操作,如果说有,它就会执行对应的操作,所以说 reducers 中的 key 的作用,其实是用来指定当前 reducer 的处理方法的,也就是类型。
好介绍清楚了,那么接下来我们就可以在这个 add 方法中处理数据了,在 add 中需要干什么呢?我这里就简单的将 count 加 1,然后将这个 count 返回出去,代码的写法就是:
/* index.js */
let homeModel = {
namespace: 'home',
state: {
count: 0
},
reducers: {
add: (state, action) => {
+ return {
+ count: state.count + action.count
+ }
}
}
}
我就添加了三行代码,返回了一个对象,这个对象中有一个 count,这个 count 的值是上一次的 count 加上这一次 action 当中你带过来你让他递增的数,这样就可以了。
紧接着再添加一个 sub 方法,这个方法的作用就是将 count 减 1,然后将这个 count 返回出去,代码的写法就是:
/* index.js */
let homeModel = {
namespace: 'home',
state: {
count: 0
},
reducers: {
add: (state, action) => {
return {
count: state.count + action.count
}
},
+ sub: (state, action) => {
+ return {
+ count: state.count - action.count
+ }
+ }
}
}
只需要将加法改成减法就可以了,到此为止关于 model 的定义就结束了,那么接下来我们就可以使用这个 model 了。
定义好了之后我们要在哪里使用这个 model 呢?是不是要在 dva 中进行使用,那怎么告诉 dva 我们要使用这个 model 呢?
很简单,通过 dva 创建出来的实例当中有一个 model 方法,这一步我称之为告诉 dva 需要使用哪个 model,那么如何告诉呢?就是通过 dva 创建出来的实例当中的 model 方法,这个方法接收一个对象,这个对象就是我们定义的 model,例如我在这里告诉 dva 我要使用 homeModel:
/* index.js */
+ app.model(homeModel)
app.model
是可以多次调用的,例如我在这里再调用一次,告诉 dva 我既要使用 homeModel,也要使用 aboutModel:
/* index.js */
+ app.model(aboutModel)
本文简单点只来一个,通过如上的介绍我是不是已经告诉 dva 我要在 dva 中使用 homeModel 了,那么接下来我们就可以在 dva 中使用 homeModel 了。
接下来我又要说到 dva 的本质了,dva 的本质是对 redux、redux-saga 进行封装,那既然是对 redux 进行封装,这个时候在 saga 中想要使用保存在 homeModel 中的数据,想要使用 homeModel 中的 reducer,是不是要通过 connect 连接起来,在回到我前面介绍的那张图:
再次观察上图,我们的 model 已经定义好了,在从图中可以看到是不是还要通过 saga 的 connect 把 model 和组件连接起来,这个时候才能在组件里面使用 Model 当中保存的数据,这个时候才能在组件当中派发任务,派发 action,去修改 Model 当中保存的数据。
所以接下来就是通过 connect 把 homeModel 和 Home 组件关联起来,过去讲解 saga 的时候已经介绍过了,是不是需要分别定义 mapStateToProps 和 mapDispatchToProps:
/* index.js */
+ // 在mapStateToProps方法中告诉React-Redux, 需要将store中保存的哪些数据映射到当前组件的props上
+ const mapStateToProps = (state) => {
+ return {
+ }
+ };
+ // 在mapDispatchToProps方法中告诉React-Redux, 需要将哪些派发的任务映射到当前组件的props上
+ const mapDispatchToProps = (dispatch) => {
+ return {
+ }
+ };
然后通过 connect 把 mapStateToProps 和 mapDispatchToProps 和 Home 组件关联起来,要使用 connect 首先需要导入 connect,前面说了 dva 是对 redux、redux-saga 进行封装,所以说 connect 是从 redux 中导入的,这里可以直接从 dva 中导入 connect:
/* index.js */
+ import {connect} from 'dva';
然后通过 connect 把 mapStateToProps 和 mapDispatchToProps 和 Home 组件关联起来:
/* index.js */
+ st AdvHome = connect(mapStateToProps, mapDispatchToProps)(Home);
connect 需要调用两次,后面一次是要连接的那个组件,前面一次要把 mapStateToProps 和 mapDispatchToProps 传给他,传给他之后,他就会把 mapStateToProps 和 mapDispatchToProps 传给你。
然后你就可以从 state 里面获取 count,在这里有一个注意点,在前面我说过将来我们是有可能定义多个 model,多个 model 中,是不是都有可能保存数据,例如,我在项目中在加一个 aboutModel:
let aboutModel = {
namespace: 'about',
state: {
num: 0
},
reducers: {
add: (state, action) => {
return {
num: state.num + action.num
}
},
sub: (state, action) => {
return {
num: state.num - action.num
}
}
}
}
我在 model 中定义了一个 num,并且定义了对应的 reducers,然后在 dva 中注册一下 model,告诉 dva 我要使用 aboutModel:
/* index.js */
+ app.model(aboutModel)
现在我们是不是在 dva 中弄了多个 model,那么在 mapStateToProps 中映射数据,我怎么知道当前映射的这个数据是 homeModel 的还是 aboutModel 当中的呢?很简单,在 homeModel 与 aboutModel 中是不是都有一个 namespace,所以我们在获取的时候该如何获取呢?
在 mapStateToProps 中会给我一个 state,要从 state 中的 namespace 中拿到我们的数据所以 mapStateToProps 的写法就是:
/* index.js */
const mapStateToProps = (state) => {
+ return {
+ count: state.home.count
+ }
};
改造为如上的写法,这个步骤总结下来就是需要从传入的 state 命名空间中拿到对应 Model 保存的数据。state.home.count
就代表着我现在要拿到的是命名空间是 home 的这个 model 当中保存的 count 这个数据。
经过了这一步我们的数据就已经有了,接下来就是完善一下派发的方法了,更改 mapDispatchToProps,在当中定义一个 increment 方法,mapDispatchToProps 中默认会传递一个 dispatch,这个 dispatch 就是用来派发任务的,然后在 increment 方法中利用 dispatch 派发一个任务,这个任务的 type 是 add:
/* index.js */
const mapDispatchToProps = (dispatch) => {
return {
+ increment() {
+ dispatch({type: 'add'});
+ }
}
};
将来在组件中调用 increment 方法的时候,就会派发一个 type 是 add 的任务,然后找到 homeModel 当中的 reducers,在 reducers 中找到 key 是 add 的这个哥们来去执行,然后在这个哥们中就可以修改 count 了,当然还要给这个派发的任务传递一些数据还要改造一下:
/* index.js */
const mapDispatchToProps = (dispatch) => {
return {
increment() {
+ dispatch({type: 'add', count: 1});
}
}
};
好了除了有 increment 方法之外,还有一个 decrement 方法,这个方法的作用就是派发一个 type 是 sub 的任务,然后在 reducers 中找到 key 是 sub 的这个哥们来去执行,然后在这个哥们中就可以修改 count 了:
/* index.js */
const mapDispatchToProps = (dispatch) => {
return {
increment() {
dispatch({type: 'add', count: 1});
},
+ decrement() {
+ dispatch({type: 'sub', count: 1});
+ }
}
};
到此为止,我们是不是就已经将 model 映射到 Home 组件了,映射到了 Home 组件,他是不是就可以传递给 Home 的 props 了,然后在 Home 组件中就可以使用了,例如在 Home 组件中我想要使用 count,那么我就可以通过 this.props.count 来获取:
/* index.js */
+ function Home(props) {
return (
<div>
+ <p>{props.count}</p>
</div>
)
}
我是不是就可以编写两个按钮,一个按钮是加,一个按钮是减,然后在点击的时候调用 increment 和 decrement 方法,这个时候就可以修改 count 了,例如我在这里编写一个按钮,按钮的内容是 +,然后在点击的时候调用 increment 方法:
/* index.js */
function Home(props) {
return (
<div>
<p>{props.count}</p>
+ <button onClick={props.increment()}>+</button>
</div>
)
}
然后再编写一个按钮,按钮的内容是 -,然后在点击的时候调用 decrement 方法:
/* index.js */
function Home(props) {
return (
<div>
<p>{props.count}</p>
<button onClick={props.increment()}>+</button>
+ <button onClick={props.decrement()}>-</button>
</div>
)
}
现在的这个代码是不是就会过去的代码一样,过去使用 reduxSage 的时候是不是也是这样的,那么现在我们使用 dva 的时候是不是就可以这样使用了,那么接下来我们来回顾一下,在回顾之前还需要完善一下代码,通过 connect 连接好了对应的组件之后返回了一个新的组件,这个组件就是 AdvHome,需要使用 AdvHome 组件,所以说在 App 组件中使用 AdvHome 组件:
function App() {
return (
<div>
+ <AdvHome/>
</div>
);
}
最终的代码如下:
import dva, {connect} from 'dva';
const app = dva();
let homeModel = {
namespace: 'home',
state: {
count: 666
},
reducers: {
add: (state, action) => {
return {
count: state.count + action.count
}
},
sub: (state, action) => {
return {
count: state.count - action.count
}
}
}
}
let aboutModel = {
namespace: 'about',
state: {
num: 0
},
reducers: {
add: (state, action) => {
return {
num: state.num + action.num
}
},
sub: (state, action) => {
return {
num: state.num - action.num
}
}
}
}
app.model(homeModel);
app.model(aboutModel);
const mapStateToProps = (state) => {
return {
count: state.home.count
}
};
const mapDispatchToProps = (dispatch) => {
return {
increment() {
dispatch({type: 'add', count: 1});
},
decrement() {
dispatch({type: 'sub', count: 1});
}
}
};
function Home(props) {
return (
<div>
<p>{props.count}</p>
<button onClick={() => {
props.increment()
}}>+
</button>
<button onClick={() => {
props.decrement()
}}>-
</button>
</div>
)
}
const AdvHome = connect(mapStateToProps, mapDispatchToProps)(Home);
function App() {
return (
<div>
<AdvHome/>
</div>
);
}
app.router(() => <App/>);
app.start('#root');
运行项目,打开浏览器,可以看到如下的效果:
将上面的内容简单的回顾一下:
.
model 方法来注册 Modelconst AdvHome = connect(mapStateToProps, mapDispatchToProps)(Home);
这行代码好了回顾完毕之后再继续往下看,我们运行项目之后页面显示的是 666,原因很简单就是我 homeModel 中的 count 我修改为了 666,为什么会显示 666 呢,就是因为我在 mapStateToProps 中明确的指定了我要拿到的是 homeModel 中的 count,所以说这里是不是就是 homeModel 中的 count,那么如果说我想要拿到 aboutModel 中的 num,那么我是不是就可以在 mapStateToProps 中修改为:
/* index.js */
const mapStateToProps = (state) => {
return {
+ count: state.about.num
}
};
回到浏览器刷新页面,可以看到页面显示的是 0:
显示没问题了,再分别点击 + 和 - 按钮,发现不行,那么为什么不行呢?原因很简单,获取数据的时候, 我们需要指定从哪一个命名空间的 Model 中获取, 但是在派发任务的时候, 我们没有指定派发到哪一个命名空间的 Model 中, 所以说问题就出现在这里,同理在派发任务的时候, 我们也需要指定要派发给哪一个命名空间的 Model。
就以我们现在的代码来看,他怎么知道 add 与 sub 这两个任务是派发到 Model 中的呢?所以说在派发任务的时候,我们也需要指定要派发给哪一个命名空间的 Model,那么如何指定呢?很简单,在 type 取值前面加上命名空间就可以了,例如我在这里指定 type 为 home/add
:
/* index.js */
const mapDispatchToProps = (dispatch) => {
return {
increment() {
- dispatch({type: 'add', count: 1});
+ dispatch({type: 'home/add', count: 1});
},
decrement() {
- dispatch({type: 'sub', count: 1});
+ dispatch({type: 'home/sub', count: 1});
}
}
};
记得将 mapStateToProps 中代码改为 homeModel,本文基于 homeModel 进行效果测试演示,运行项目,打开浏览器,可以看到如下的效果:
最后附上完整代码:
import dva, {connect} from 'dva';
const app = dva();
let homeModel = {
namespace: 'home',
state: {
count: 666
},
reducers: {
add: (state, action) => {
return {
count: state.count + action.count
}
},
sub: (state, action) => {
return {
count: state.count - action.count
}
}
}
}
let aboutModel = {
namespace: 'about',
state: {
num: 0
},
reducers: {
add: (state, action) => {
return {
num: state.num + action.num
}
},
sub: (state, action) => {
return {
num: state.num - action.num
}
}
}
}
app.model(homeModel);
app.model(aboutModel);
const mapStateToProps = (state) => {
return {
count: state.home.count
}
};
const mapDispatchToProps = (dispatch) => {
return {
increment() {
dispatch({type: 'home/add', count: 1});
},
decrement() {
dispatch({type: 'home/sub', count: 1});
}
}
};
function Home(props) {
return (
<div>
<p>{props.count}</p>
<button onClick={() => {
props.increment()
}}>+
</button>
<button onClick={() => {
props.decrement()
}}>-
</button>
</div>
)
}
const AdvHome = connect(mapStateToProps, mapDispatchToProps)(Home);
function App() {
return (
<div>
<AdvHome/>
</div>
);
}
app.router(() => <App/>);
app.start('#root');
通过本文的学习,您可以掌握以下知识点:
🐤如果您觉得本文对您有所帮助,欢迎点赞、收藏或分享,您的支持是我创作的最大动力!
这篇文章的内容就介绍到这里,期待我们下次的相遇。感谢您花时间阅读,如果有任何问题或想法,欢迎在评论区留言。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。