【Vue学习笔记_17】Vuex状态管理
Vuex初识
Vuex:一个专为Vuejs应用程序开发的状态管理模式,应用于多界面状态管理。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
状态管理:可以简单的理解为把需要多个组件共享的变量存储在一个对象里面,然后将这个对象放在顶层的Vue实例中,让其他组件可以使用。
自己封装一个对象也可以实现状态管理,但是要想所有的属性都做到响应式就比较麻烦了。Vuex就是为了提供这样一个在多个组件之间共享状态的插件。
Vuex基本思想:全局单例模式。将需要共享的状态抽取出来,统一进行管理。之后每个视图按照规定好的格式进行访问和修改等操作。而Vuex就是提供统一管理的工具。
有哪些状态需要我们在多个组件之间共享呢?
- 用户的登录状态(token)、用户名、头像、地理位置等
- 收藏、购物车中的商品
Vuex安装:npm install vuex --save
Vuex核心概念:State,Getters,Mutations,Actions,Modules
Vuex基本使用
第一步,在store/index.js文件中创建并导出Vuex.Store实例
import Vuex from 'vuex'
//1.Vue.use(插件):安装Vuex插件
Vue.use(Vuex)
//2.创建Vuex.Store实例
const store = new Vuex.Store({
state: {},
getters: {},
mutations: {},
actions: {},
modules: {}
})
//3.导出实例
export default store
第二步,在main.js文件的Vue实例中挂载创建的store实例
import Vue from 'vue'
import App from './App'
import store from './store'
new Vue({
el: '#app',
store,
render: h => h(App)
})
第三步,项目中任何一个组件都可以通过$store访问到该实例
<h2>{{$store.state.counter}}</h2>
Vuex-State
如果状态信息保存到多个Store对象中,那么之后的管理和维护都会变得特别困难,所以Vuex使用了单一状态树(Single Source of Truth,单一数据源)来管理应用层级的全部状态。也就是说,一个项目里有一个Store就可以了。
//store/index.js
state: {
counter: 0,
students: [
{name: 'Perry', age: 19},
{name: 'Bob', age: 20},
{name: 'Alice', age: 21}
]
}
Vuex-Getters
类似组件的计算属性。有时候需要从Store中获取一些处理后的State,就可以使用Getters。
- getters方法的第一个参数是state,组件使用的时候不用传,默认是$store.state,即当前store实例的state
- getters方法的第二个参数是getters,组件使用的时候也不用传,默认是$store.getters,即当前store实例的getters
- getters方法如果需要自定义参数,可以在方法里再返回一个有参函数,组件使用的时候就相当于调用了这个函数,就可以传递相应的参数了
//store/index.js
getters: {
powerCounter(state) {
return state.counter * state.counter
},
more20stu(state) {
return state.students.filter(s => s.age>20)
},
more20stuLength(state, getters) {
return getters.more20stu.length
},
moreAgeStu(state) {
return age => {
return state.students.filter(s => s.age>age)
}
}
}
在组件中使用:
<h2>{{$store.getters.powerCounter}}</h2>
<h2>{{$store.getters.more20stu}}</h2>
<h2>{{$store.getters.more20stuLength}}</h2>
<h2>{{$store.getters.moreAgeStu(18)}}</h2>
Vuex-Mutations
Devtool是Vue开发者工具,如果Vuex的State是通过提交Mutation修改的,那么Devtool可以跟踪到State的变化过程;如果直接操作State,则跟踪不到。所以官方指定Vuex的Store状态的更新唯一方式是提交Mutations。
Mutations主要包括两部分:
- 事件类型(type),也就是提交和定义的回调参数的名称
- 回调函数(handler),第一个参数是state
//store/index.js
mutations: {
increment(state) {
state.counter++
}
}
在组件中使用:
methods: {
add() {
this.$store.commit('increment')
}
}
Mutations传递参数
在通过Mutations更新数据的时候,有时候希望携带一些额外的参数,这些参数被称为Mutations的载荷(Payload)。如果需要传递多个参数,通常会放到一个对象中传递,也就是Payload是一个对象,然后再从对象中取出相关信息。
//store/index.js
mutations: {
incrementCount(state, count) {
state.counter += count
},
addStu(state, stu) {
state.students.push(stu)
}
}
在组件中使用:
methods: {
addCount(count) {
this.$store.commit('incrementCount', count)
},
addStu() {
const stu = {name: 'Mary', age: 23}
this.$store.commit('addStu', stu)
}
}
总结mutations方法的参数:
- 第一个参数是state,组件使用的时候不用传,默认是$store.state,即当前store实例的state
- 第二个参数是payload,组件通过commit提交事件时携带的信息,可选参数
Mutations提交风格
上面是一种普通的提交风格,Vue还提供了另外一种风格:它是一个包含事件类型type属性的对象,mutations中的处理方式是将整个commit的对象作为payload。
//store/index.js
mutations: {
incrementCountSpec(state, payload) {
state.counter += payload.count
}
}
在组件中使用:
methods: {
addCount(count) {
this.$store.commit({
type: 'incrementCountSpec',
count
})
}
}
Mutations响应规则
Vuex的state是响应式的:state中定义的属性会被加入到响应式系统中,而响应式系统会监听属性的变化,当属性发生变化时,会通知所有组件中用到该属性的地方,让界面更新。
更多关于Vue响应式原理,可以看这篇笔记:Vue响应式原理简述
但这要求我们必须遵守一些Vuex的相应规则:只有在state中初始化好的属性是响应式的;当给state中的对象添加新属性或删除属性时,需要使用Vue.set()或Vue.delete(),否则不是响应式的。
Vue.set(state.info, 'address', 'Nanjing')
Vue.delete(state.info, 'height')
Mutations类型常量
在mutations中,我们定义了很多事件类型,也就是方法名称。当方法过多时,需要花费大量精力去记住这些方法,还可能会出现写错的情况。因此,最好将事件类型定义成常量。
在store目录下创建mutations-types.js文件,专门用于导出常量事件类型:
//store/mutations-types.js
export const INCREMENT = 'increment'
在store/index.js中使用:
//store/index.js
import {
INCREMENT
} from './mutations-types'
mutations: {
[INCREMENT](state) {
state.counter++
}
}
在组件中使用:
import {
INCREMENT
} from './store/mutations-types'
methods: {
add() {
this.$store.commit(INCREMENT)
}
}
Vuex-Actions
Vuex要求Mutations中的方法必须是同步的,devtools可以帮助我们捕捉mutations的快照。但如果是异步操作,devtools将不能很好地追踪这个操作的state变化。
但在某些情况下,我们确实希望在Vuex中进行一些异步操作,比如网络请求。Actions类似于Mutations,是用来代替Mutations进行异步操作的 。
actions方法的参数:
- 第一个参数是context,组件使用的时候不用传,默认是$store,即当前store实例
- 第二个参数是payload,组件通过dispatch提交事件时携带的信息,可选参数
//store/index.js
actions: {
aUpdateInfo(context, payload) {
return new Promise(((resolve, reject) => {
setTimeout(() => {
context.commit('updateInfo')
console.log(payload)
resolve('回调成功的参数')
}, 1000)
}))
}
}
在组件中使用:
updateInfo() {
this.$store
.dispatch('aUpdateInfo', '携带信息')
.then(res => {
console.log('成功');
console.log(res);
})
}
注:this.$store.dispatch这部分相当于调用 aUpdateInfo方法返回的 Promise,所以后面接 then。
更多关于Promise对象以及其他ES6常用语法,可以看这篇笔记:ES6常用语法总结
Vuex-Modules
Vuex使用单一状态树,意味着所有状态都会交给一个state来管理,当应用变得非常复杂时,state就可能变得相当臃肿。为了解决这个问题,Vuex允许我们将store按模块划分,每个模块拥有自己的state、mutations、actions、getters等。
//store/index.js
const moduleA = {
state: {
name: 'A'
},
mutations: {
updateName(state, payload) {
state.name = payload
}
},
actions: {
//context:包括本级模块的commit、getters、rootState等
aUpdateName(context) {
setTimeout(() => {
context.commit('updateName', 'zhangsan')
}, 1000)
}
},
getters: {
fullname(state) {
return state.name + '111'
},
//getters:取本级模块的getters、rootState:取上级模块的state
fullname2(state, getters, rootState) {
return getters.fullname + rootState.counter
},
}
}
const moduleB = {
...
}
const store = new Vuex.Store({
state: {
counter: 0
},
modules: {
a: moduleA,
b: moduleB
}
})
在组件中使用:
<template>
<h2>{{$store.state.a.name}}</h2>
<h2>{{$store.getters.fullname}}</h2>
<h2>{{$store.getters.fullname2}}</h2>
<button @click="asyncUpdateName">异步更新name</button>
<button @click="updateName">更新name</button>
</template>
<script>
updateName() {
this.$store.commit('updateName', 'frog')
},
asyncUpdateName() {
this.$store.dispatch('aUpdateName')
}
</script>
注:在组件中使用 $store.state.模块名.属性名
获取模块中的属性,而使用 getters、commit、dispatch时不用注明是哪个模块的属性或方法。
Vuex-store目录结构
当getters、mutations、actions内容很多的时候,可以把每部分单独提取成一个文件,重构store目录:
//store/index.js
import moduleA from './modules/moduleA'
import moduleB from './modules/moduleB'
import mutations from './mutations'
import actions from './actions'
import getters from './getters'
const state = {
...
}
const store = new Vuex.Store({
state,
mutations,
getters,
actions,
modules: {
a: moduleA,
b: moduleB
}
})
export default store
//store/mutations.js
export default {
...
}
//store/modules/moduleA.js
export default {
state: {...},
mutations: {...},
actions: {...},
getters: {...}
}