对于小型应用来说,完全没用必要引入状态管理,因为这会带来更多的开发成本。
对于组件化开发来讲,大型应用的状态往往跨越多个组件。在多层嵌套的父子组件之间传递状态已经十分麻烦,而Vue更是没用为兄弟组件提供直接共享数据的方法。
基于这个问题,许多框架提供了解决方案——使用全局的状态管理器,将所有分散的共享数据交由状态管理器保管。
-
vuex
适用于大型项目中,在小型项目中使用sessionStorage
和localStorage
。
一、什么是vuex?
1.1 vuex的定义
vuex
就是用来集中管理组件状态,称为组件状态管理模式,适合组件中存在大量密集的数据传递的情况。
- 每一个Vuex应用的核心都是一个
store
(仓库),你也可以理解它是一个“非凡的全局对象”。与普通的全局对象不同的是,基于Vue数据与视图绑定的特点,当store中的状态发生变化时,与之绑定的视图也会被重新渲染。 - 渲染是一个单向的过程,因为store中的状态不允许被直接修改。
- 改变store中的状态的唯一途径就是显式的提交(commit)
mutation
,这可以让我们方便的跟踪每一个状态的变化。
1.2 什么数据才需要存储到Vuex中?
- 一般情况下,只有组件之间共享的数据,才有必要存储到
vuex
(store)中; - 对于组件中的私有数据,依旧存储在组件自身的data中即可。
1.3 使用Vuex统一管理的好处
- 能够在vuex中集中管理共享的数据,易于开发和后期的维护。
- 能够高效的实现组件之间的数据共享,提高开发的效率。
- 存储在vuex中的数据都是响应式的,能够实时保持数据与页面的同步。
二、Vuex核心概念
Vuex有5个重要的概念:State
,Getter
,Mutation
,Action
,Module
。
1. State
State用于维护所有应用层的状态,并确保应用只有唯一的数据源(SSOT,Single Source of Truth)。
- 用法:
//创建store数据源,提供唯一公共数据
const store = new Vuex.Store({
state: {
count: 0
}
})
组件访问State中数据的方式有两种:
this.$store.state.count
-
mapState
辅助函数映射下来
import { mapState } from 'vuex'
export default{
computed: {
...mapState(['count']) //...是ES6中的对象展开运算符
}
}
2.Getter
Getter维护由State派生的一些状态,这些状态随着State状态的变化而变化。
- 与计算属性一样,Getter中的派生状态在被计算之后会被缓存起来。
- 当重复调用时,如果依赖的状态没有变化,那么vuex不会重新计算派生状态的值,而是直接采用缓存值。
- 用法:
const store = new Vuex.Store({
state: {
count: 0
},
getters: {
showNum (state) {//Vuex为其注入state对象
return '当前最新的数量是【'+ state.count +'】'
}
}
})
Getter有两种使用方式:
this.$store.getters.showNum
- 使用
mapGetters
辅助函数映射
import { mapGetters } from 'vuex'
export default {
computed:{
...mapGetters{[ 'showNum']}
}
}
3. Mutation
Mutation提供修改State状态的方法。
1.只能通过
mutation
变更Store
数据,不可以直接操作Store
中的数据
2.通过这种方式虽然操作起来稍微繁琐一些,但是可以集中监控所有数据的变化
- 用法:
new Vuex.Store ({
state : {
count: 0
},
Mutations : {
addCount (state, num){
state.count += num || 1
}
}
})
mutations有两种触发方式:
- 使用
store.commit
来提交mutation
methods:{
addCount () {
this.$store.commit('addCount')
}
}
- 使用
mapMutations
辅助函数映射
import { mapState,mapMutations } from 'vuex'
export default {
computed: {
...mapState(['count'])
},
methods: {
...mapMutations(['addCount']),
...mapMutations({ //为mutation赋别名,注意冲突,此方法不常用
increaseCount: 'addCount'
})
}
}
4. Action
Action用于处理异步任务。
Action类似于Mutation,不同在
- Action不能直接修改状态,只能通过提交mutation来修改
- Action可以包含异步操作
- 用法:
new Vuex.Store({
state: {
count: 0
},
mutations: {
addCount (state, num){
state.count += num || 1
}
},
actions: {
// context具有和store实例相同的属性和方法
// 可以通过context获取state和getters中的值,或者提交mutation和分发其他的action
addCountAsync(context, num){
setTimeout(()=>{
context.commit('addCountAsync', num || 1000)
}, num || 1000)
}
}
})
Action也有两种触发方式:
- 使用
store.dispatch
来分发action
methods: {
addCountAsync (num) {
this.$store.dispatch('addCountAsync ', num)
}
}
- 使用
mapActions
辅助函数映射
`import { mapState, mapActions } from 'vuex'`
export default{
computed: {
...mapState(['count'])
},
methods: {
...mapActions([ 'addCountAsync']),
...mapActions({ // 为action赋别名,注意冲突,此方法不常用
increaseCountAsync: 'addCountAsync'
})
}
}
5. Module
由于使用单一状态树,当项目的状态非常多时,store对象就会变得十分臃肿。
因此,Vuex允许我们将store分割成模块(Module),每个模块拥有独立的State、 Getter、 Mutation和Action,模块之外还可以嵌套模块,每一级都有着相同的结构。
- 用法:
//定义模块
const counter = {
namespaced: true, // 定义为独立的命名空间
state: {
count: 0
},
getters: {
// 在模块中,计算方法还会具有 rootState、rootGetters参数以获取根模块中的数据
tentimesCount (state, getters, rootState, rootGetters) {
console.log(state, getters, rootState, rootGetters)
return state.count * 10
}
},
mutaions: {
addCount (state, num) {
state.count += num || 1
}
},
actions: {
// context具有和store实例相同的属性和方法
// 可以通过context获取state和getters中的值,或者提交mutation和分发其他的action
// 在模块中,context还会具有 rootState、rootGetters参数以获取根模块中的数据
addCountAsync (context, num) {
setTimeout(()=>{
context.commit('addCountAsync', num || 1000)
}, num || 1000)
}
}
}
新建仓库
new Vuex.store({
modules: { // 注册模块
counter
}
})
在组件中,模块的使用方法如下:
import {mapState, mapGetters, mapMutations , mapActions } from 'vuex'
export default {
computed: {
//辅助函数的第一个参数为模块的名称
...mapState('counter', ['count'])
...mapGetters('counter', ['tenTimesCount'])
},
methods: {
...mapMutations('counter', ['addCount'])
...mapActions('counter', ['addCountAsync'])
}
}
【总结】
作为一个状态管理器,首先要有保管状态的容器——State
;
为了满足衍生数据和数据链的需求,从而有了Getter
;
为了可以“显式地”修改状态,所以需要Mutation
;
为了可以“异步地”修改状态(满足AJAX等异步数据交互),所以需要Action
;
最后,如果应用有成百上千个状态,放在一起会显得十分庞杂,所以需要分模块管理(Module
)。
三、在项目中使用Vuex
- 安装插件
npm install vuex --save-dev
或者cnpm install vuex --save-dev
使用npm或者cnpm都可以 - 创建文件
在src目录下创建store
、store/index.js
、store/modules
、store/modules/counter.js
其中,
store
是我们进行Vuex仓库开发的工作目录,
store/index.js
是仓库的输出文件,
store/modules
目录用于放置各个模块,
store/modules/counter.js
文件是一个加数器模块。- store/modules/counter.js中的代码如下:
export default { namespaced: true, // 定义为独立的命名空间 state: { count: 0 }, getters: { // 在模块中,计算方法还会具有 rootState、rootGetters参数以获取根模块中的数据 tentimesCount (state, getters, rootState, rootGetters) { console.log(state, getters, rootState, rootGetters) return state.count * 10 } }, mutaions: { addCount (state, num) { state.count += num || 1 } }, actions: { // context具有和store实例相同的属性和方法 // 可以通过context获取state和getters中的值,或者提交mutation和分发其他的action // 在模块中,context还会具有 rootState、rootGetters参数以获取根模块中的数据 addCountAsync (context, num) { setTimeout(()=>{ context.commit('addCountAsync', num || 1000) }, num || 1000) } } }
- store/index.js中的代码如下:
上面已经实例化了一个Vuex仓库并构建了一个加数器模块。之后,我们要在Vue实例中引入这个仓库,这还需要修改两个文件: webpack的入口文件main.js和单组件文件 components/HelloWorld.vue.import Vue from 'vue' import Vuex from 'vuex' import counter from './modules/counter' //引入加数器模块 Vue.use(Vuex) //安装插件 export default new Vuex.Store {( //实例化Vuex仓库 modules: { counter } )}
- 修改后的main.js的代码如下:
到此就已经将仓库注册到了Vue实例中了,之后就可以在项目的任意文件中使用了。import Vue from 'vue' import App from './App' import router from './router' import store from './store' Vue.config.productionTip = false new Vue({ el: #app, router, store, components: {App}, template: '
' })