数据管理
在实际的开发中,经常会遇到多个组件共享一个数据的场景
面对这种场景,会产生至少以下两个问题:
- 多个组件如何共享同一份数据?
- 如果某个组件修改了数据,如何让其他组件知道?
面对这种问题,一个可行的解决办法,就是让数据提升
所谓数据提升,就是把数据提升到更加顶层的组件,让顶层的组件通过属性下发数据,而当组件想改变数据的时候,又通过事件一层层向上传递
使用这种方式,虽然可以解决问题,但是带来了更多的问题:
- 书写特别繁琐
- 依赖极其混乱:某些组件本来并不需要一些数据,但是由于它的子组件需要,自己也必须要接收
- 无谓的重新渲染:如果数据变化了,并不依赖这些数据的组件也会被迫重新渲染
为了解决这些问题,vuex
出现了
vuex
专门用于解决共享数据问题,它的思路和上述一致,也是将数据提升到顶层,不过它使用了一些特别的技巧,不仅让组件的依赖更加清晰,当数据变动时,仅冲渲染依赖该数据的组件
但是要注意,并非所有数据都需要让vuex
管理,通常vuex只管理那些需要被组件共享的数据
在实际的开发中,一些逻辑特别复杂的数据,尽管不共享,也可能提取到vuex中进行管理
安装
在页面中引入vuex
库
该库提供了一个构造函数Vuex.Store
,通过该构造函数,即可创建一个数据仓库
var store = new Vuex.Store({
// 仓库数据配置
})
将得到的对象,配置到vue
中,即可在vue
中注入vuex
的功能
new Vue({
// 其他配置
store
})
现在,你的vue
应用拥有了使用数据仓库的能力
初始化状态
vuex
将仓库中的数据称之为state 状态
在vuex
的配置中使用属性state
即可配置仓库中的状态初始值
var store = new Vuex.Store({
state: { // 状态初始值,这些数据可以被所有组件共享
onlineNumber: 0, // 在线人数
movies: { // 电影数据
data: [],
total: 0,
page: 1,
limit: 3,
isLoading: false
}
}
})
在组件中使用该数据非常简单
vuex
向vue实例
中注入了一个属性$store
,通过该属性即可得到仓库中的数据
this.$store.state // 仓库中的状态
<h1>
在线人数:{{$store.state.onlineNumber}}
</h1>
虽然这种写法看上去很方便,但是书写繁琐,并且不利于从阅读上明确依赖关系
更多的时候,我们会将组件对数据的依赖,明确的声明在组件的computed
配置中
var Comp = {
computed:{
onlineNumber(){
return this.$store.state.onlineNumber;
}
},
template: `
<h1>
在线人数:{{onlineNumber}}
</h1>
`
}
为了更加方便的让我们书写computed
,vuex
提供了一个函数vuex.mapState
var Comp = {
computed: Vuex.mapState(["onlineNumber"]), // 等同于上面的代码
template: `
<h1>
在线人数:{{onlineNumber}}
</h1>
`
}
vuex.mapState
有非常非常多的用法,目的只有一个:更加方便的书写computed
状态模块化
通常情况下,一个vue实例,只有一个数据仓库
如果仓库中的所有状态都放在一起,既不利于管理,也容易产生名称的冲突
实际开发中,仓库中的状态往往是分为多个模块的
var movies = { // 电影模块
state: { // 电影模块的初始状态
data: [],
total: 0,
page: 1,
limit: 3,
isLoading: false
}
}
var online = { // 在线统计模块
state: {
number: 0
}
}
// 合并状态模块
var store = new Vuex.Store({
modules:{
movies,
online
}
})
从此,store中的状态如下:
{
online:{
number:0
},
movies:{
data: [],
total: 0,
page: 1,
limit: 3,
isLoading: false
}
}
在使用时,可以通过下面的方式映射到computed
var Comp = {
computed: Vuex.mapState("online", ["number"]), // 第一个参数是模块名称(命名空间名)
template: `
<h1>
在线人数:{{number}}
</h1>
`
}
数据变化
数据不可能永远不变,但数据也不会无缘无故的变化
每一次数据变化都有原因,在某种原因的驱使下,数据从一种状态变化到另一种状态
vuex
把数据的变化过程,称之为mutation
mutation
在代码中表现为一个函数,配置在mutations
中
var movies = { // 电影模块
state: { // 电影模块的初始状态
data: [],
total: 0,
page: 1,
limit: 3,
isLoading: false
},
mutations:{
/**
* setPage: mutation的名称
* oldState: 原来的状态
* payload: 载荷。为了变化新状态,需要的额外信息
*/
setPage(oldState, payload){
oldState.page = payload.page;
},
setResp(oldState, payload){
oldState.data = payload.data;
oldState.total = payload.total;
}
}
}
看上去,mutation似乎非常简单,仅仅是完成一个赋值即可
但它的意义是非凡的
它至少向外表达了以下信息:
- 我的数据可以发生哪些变化
- 除了这些变化之外,不可能再发生任何其他变化
- 这些变化是原子性的,不可分割的。比如
setResp
,必须同时变化data
和total
,不可能分割
mutation的存在,让状态变化变得统一、可控
通过mutation触发数据的变化,称之为commit 提交
提交mutation
,是数据发生变化的唯一原因
在代码层面,提交mutation
通过下面的api完成
// 触发setPage运行,payload为2
this.$store.commit("setPage", {page:2});
this.$store.commit({
type: "setPage",
page: 2
})
当仓库中的状态变化时,所有依赖该状态的组件都会自动重新渲染
追踪数据变化
mutation
让追踪数据变化成为了可能,这非常有利于调试
chrome
扩展程序vue-devtools
同时,这也要求我们在编写mutation
时,要注意:
mutation 中不要出现异步代码,否则会让状态的变化难以追踪
启用命名空间
由于数据模块后,非常容易出现mutation
重名
启用命名空间即可解决该问题
var movies = { // 电影模块
namespaced: true, // 启用命名空间
state: {
data: [],
total: 0,
page: 1,
limit: 3,
isLoading: false
},
mutations:{
setPage(oldState, payload){
oldState.page = payload.page;
},
setResp(oldState, payload){
oldState.data = payload.data;
oldState.total = payload.total;
}
}
}
命名空间启用后,commit
时,mutation
的名称前需要加上命名空间
this.$store.commit("movies/setPage", { page: 1 })
异步处理
由于mutation
不允许使用异步代码,所以异步代码需要单独处理
vuex
把异步处理称之为action
在action
中不允许直接更改状态,但允许提交mutation
var online = {
namespaced: true,
state:{
number: 0
},
mutations:{
add(state){
state.number++;
}
},
actions:{ //处理异步
asyncAdd(context){
// 可以把context当作是store对象
setTimeout(function(){
context.commit("add")
}, 1000)
}
}
}
在action
中,你可以通过参数context
参数,获取store
实例
但值得注意的是,如果开启了namespaced
,则context
和store
有以下不同:
context.state
是当前模块的状态,而不是整个仓库的状态context.rootState
才是整个仓库的状态context.commit
可省略当前命名空间
如果要触发一个action
,需要使用下面的API
this.$store.dispatch("online/asyncAdd");