官方解释:Vuex是一个专为Vue.js应用程序开发的状态管理模式。
-
它采用 集中式存储管理 应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
-
Vuex也集成到Vue官方的调试工具devtools-extension
状态管理到底是什么?:简单地将其看成把多个组件共享的变量存储在一个对象里面
但是,有什么状态是我们需要在多个组件间共享的?
- 用户的登录状态、用户名称、头像、地理位置信息等等。
- 商品的收藏、购物车中的物品等等
- 这些状态信息,我们可以放在统一的地方,对它进行保存和管理,而且他们还是响应式的。
单组件的状态管理
单个组件中进行状态管理是一件非常简单的事情,Vue已经帮我们做好了单个组件的状态管理,其中 state 是 data 中的属性。
多组件的状态管理
在多组件的状态管理中,多个视图都依赖同一个状态(一个状态改了,多个界面需要进行更新),不同界面的Actions都有修改同一个状态的需求,而我们只需要按照vuex的操作规范进行访问和修改等操作,这就是Vuex背后的基本思想。
图中,Vue Components代表页面,虚线框包含Vuex的处理逻辑,Backend API表示异步网络请求,Devtools表示状态追踪工具。
Talk is cheap,show me the code.
安装 vuex插件
插件配置
- 下载 vuex 插件
npm install vuex --save
- 在src目录下创建一个store目录,并在目录下创建index.js文件
- 编写index.js文件,在划分多模块的情况下index.js常用于组装文件的。
import Vue from 'vue'
import Vuex from 'vuex'
// 安装插件
Vue.use(Vuex)
// 创建对象
const store = new Vuex.Store({ // 这里的 Store 首字母要求大写,否则报错
state:{},
mutations:{},
getters:{},
actions:{},
modules:{}
})
// 导出对象
export default store
- 编写main.js文件
import Vue from 'vue'
import App from './App'
import store from './store'
new Vue({
el:'#app',
store,
render:h=>h(App)
})
这样,我们就可以通过this.$store的方式,获取到这个store对象了。
如何修改vuex中state的值
// state
state:{
counter:100
}
// 在mutations中定义两个修改 counter 的方法
mutations:{
increment(state){
state.counter++
},
decrement(state){
state.counter--
}
}
// 组件中展示
<div id='app'>
{{$store.state.counter}}
<button @click="addtion">自增/button>
<button @click="subtraction">自减/button>
...
</div>
methods:{
addtion(){
this.$store.commit('increment') // 提交给mutation中的increment方法
},
subtraction(){
this.$store.commit('decrement') // 提交给mutation中的decrement方法
}
}
好的,这就是使用Vuex最简单的方式了。
我们来对使用步骤,做一个简单的小结:
- 提取出一个公共的store对象,用于保存在多个组件中共享的状态
- 将store对象放置在new Vue中,这样可以保证在所有的组件中都可以使用到
- 在其他组件中访问store对象中保存的状态
- 通过this.$store.state属性的方式来访问状态
- 通过**this.$store.commit('mutations中的方法')**来修改状态
注意事项:
- 我们通过提交mutations的方式,而非直接改变this.$store.state.counter
- 这是因为Vuex可以更明确的追踪状态的变化,所以不要直接改变this.$store.state.counter
getters 一二
getters属性
getters属性类似于计算属性,简化表达式的书写
// state
state:{
students:[
{id:0,name:'yang',age:25},
{id:1,name:'cai',age:18},
{id:2,name:'zheng',age:20},
{id:3,name:'jiong',age:10}
]
}
// 定义getters
getters:{
more19stu(state){
return state.students.filter(s=>s.age > 19)
}
}
// 使用getters
{{$store.getters.more19stu}}
getter属性作为参数
getters:{
more20stu:state => {
return state.students.filter(s => s.age >= 20)
},
more20stucount:(state,getters) => { // 第二个参数为getters
return getters.more20stu.length
}
}
现在提出一个新的需求,年龄不确定,也就是不是固定值20,怎么解决?
其中,moreAgeStuCount 返回年龄超过 age 的元素
{{$store.getters.moreAgeStuCount(20)}}
// 核心代码 令getters moreAgeStuCount属性作为闭包函数并且能访问state
moreAgeStuCount: state => {
return function(age){
return state.students.filter(s => s.age > age)
}
}
Mutation为修改State而生
Vuex的store更新状态的方式只有一种:提交Mutation
若需改变state中的数据却没有相应的函数,那就在mutations创造一个 If not, create
Mutation主要包括两部分:
- 事件类型(type,字符串)
- 回调函数(handler),该回调函数的第一个参数就是state
// mutation的定义方式
mutations:{
increment(state){
state.count++
}
}
// 通过mutation更新
addtion:function(){
this.$store.commit('increment')
}
普通的提交方式:在通过mutation更新数据的时候,有时候我们希望携带额外参数
- 额外参数被称为是mutation的载荷(Payload)
// index.js文件中
decrement(state,payload){
state.count -= payload
}
// 组件中
substraction:function(){
this.$store.commit("decrement",2) // 只传递一个参数
}
但是,如果参数不只一个呢?
- 比如我们有很多参数需要传递
- 这个时候,我们通常会以对象的形式传递,也就是payload这时是一个对象
- 再从对象中取出相关的信息
// index.js文件中
mutations:{
changecount(state,payload){
state.count = payload.count
}
}
// 组件中
altcount:function(){
this.$store.commit("changecount",{count:0})
}
有一点值得注意的是,上面的通过commit进行提交是一种普通的方式
this.$store.commit('increment')
this.$store.commit("decrement",2)
this.$store.commit("changecount",{count:0})
...
Vue还提供了另外一种风格,它是一个包含type属性的对象
// index.js文件中
mutations:{
changecount(state,payload){
state.counter += payload.count
}
}
// payload 实际打印如下
{
count: 5
type: "decrement"
}
// 组件中
let count = 5
this.$store.commit({ // 只传递一个包含type属性对象参数
type:'changecount',
count
})
Mutation还有一种常量类型的使用方式,类型(type)采用统一常量的形式记录在一个文件中方便其他组件使用。
// mutation-types.js文件
export const INCREMENT = 'increment' // counter自增
// action.js文件使用
import {INCREMENT} from './mutation-types.js'
export default {
actAddcount({commit},num){
commit({
type:'INCREMENT',
num
})
}
}
// mutation.js文件使用
import {INCREMENT} from './mutation-types.js'
export default {
[INCREMENT](state,payload){
state.counter += payload.num
}
}
Actions异步操作
Vuex要求我们Mutation中的方法必须是同步方法,不要在mutation中进行异步操作,主要原因是当我们使用devtools时,devtools工具可以帮助我们捕捉mutation的快照,若是异步操作,那么devtools将不能跟踪操作什么时候会被完成从而发生数据不一致的情况。
若修改State是同步操作的话,可以跳过actions,直接提交Mutations
但是某些情况,我们确实希望在Vuex中进行一些异步操作,比如网络请求,这时action用来代替mutation进行异步操作。
// action的使用
actions:{
// context 上下文,而mutation和getter中函数的第一个参数是state
actUpdateInfo(context){
}
}
// 组件中触发事件
cpnUpdateInfo(){
this.$store.dispatch('actUpdateInfo').then(res=>{console.log(res)}) // 打印“成功修改状态”
}
如果异步函数执行完想告诉组件,可借用ES6 promise的方式
actions:{
// 第一个参数是context(上下文),而mutation和getter中函数的第一个参数是state
actUpdateInfo(context){
return new Promise((resolve,reject)=>{
//在vuex内部实现,返回的promise对象将赋予页面中组件dispatch后的结果
setTimeout(()=>{ // actions函数中使用异步代码
context.commit('updateInfo') //仍然不能跳过mutation去修改state
resolve('成功修改状态')
},1000)
})
}
}
cpnUpdateInfo(){
this.$store.dispatch('actUpdateInfo').then(res=>console.log(res))
}
mapState,mapGetters,mapActions,mapMutations
// 组件中使用mapGetters,mapState,mapActions,mapMutations
<script>
import {mapGetters,mapState,mapActions,mapMutations} from 'vuex'
export default {
name:'...',
methods:{
// ES6语法 使用对象展开运算符将此对象混入到外部对象中
...mapActions(['actUpdateInfo']),
...mapMutations(['increment'])
/*
this.actUpdateInfo 将映射为 this.$store.dispatch('actUpdateInfo')
this.increment 将映射为 this.$store.commit('increment')
*/
}
computed:{
...mapState(['counter']),
/*
...mapState({
cc:'counter' // 取别名,mapGetters,mapActions,mapMutations也可以
// 且 this.cc 将映射为 this.$store.state.counter
})
*/
...mapGetters(['more20stu','moreAgeStuCount']), // 访问方式与状态变量一样
test(){...}
}
}
</script>
Modules 模块化切割
module是模块的意思,为什么在Vuex中我们要使用模块呢?Vue使用集中式状态管理,即单一数据源意味很多状态都会交给Vuex来管理,当应用变得非常复杂时,store对象就有可能变得相当臃肿,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的state、mutations、actions、getters、甚至是嵌套子模块——从上至下进行同样方式的分割。
vuex 配置文件信息
import Vue from 'vue'
import Vuex from 'vuex'
// 安装插件
Vue.use(Vuex)
const moduleA = {
namespaced:true, // 命名空间关键属性
state:{
module:'moduleA', // 模块A 和 模块B 中的state属性可相同
name:'张三',
num:20
},
getters:{
Aget1(state){
return "Hello 我是 "+state.name+",今年我"+state.num+"岁啦" // 访问的是本地的 state
},
Aget2(state,getters){
return "下面由我做一个自我介绍" + getters.Aget1
},
Aget3(state,getters,rootState){
return "这是我的大总管 " + rootState.name + ",今年有" + rootState.num + "岁啦"
},
Aget4(state,getters,rootState,rootGetters){
return rootGetters.say
}
},
mutations:{
add(state,payload,a){
state.num += payload.number
}
},
actions:{
actadd({commit},payload){
commit('add',payload)
}
}
}
const moduleB = {
namespaced:true, // 命名空间关键属性
state:{
module:'moduleB', // 模块A 和 模块B 中的state属性可相同
name:'李四',
num:100
},
getters:{
Bget1(state){
return "Hi 我是 "+state.name+",今年我"+state.num+"岁啦"
}
},
mutations:{
add(state,payload){
state.num += payload.num
}
},
actions:{}
}
// 创建对象
const store = new Vuex.Store({
state: {
name:'帅帅囧',
counter: 100,
num:1000
},
mutations: {
increment(state) {
state.counter++
},
decrement(state,payload) {
},
updateInfo(state){
state.counter -= 5
}
},
getters: {
say(state){
return "北京欢迎你"
}
},
actions:{
// 第一个参数是context(上下文),而mutation和getter中函数的第一个参数是state
actUpdateInfo(context,msg){ //此处context解构即{commit},则下面代码修改成commit(...)
console.log(msg);
return new Promise((resolve,reject)=>{
//在vuex内部实现,返回的promise对象将赋予页面中组件dispatch后的结果,告诉app组件修改成功了,好让app组件做出下一步动作
setTimeout(()=>{ // actions函数中使用异步代码
context.commit('updateInfo') //仍然不能跳过mutation去修改state
resolve('成功')
},1000)
})
}
},
modules: {
a:moduleA, // 通过this.$store.state.a访问这个模块的state
b:moduleB // 通过this.$store.state.b访问这个模块的state
}
})
// 导出对象
export default store
划分模块后的访问方法
// app组件
<template>
<div id='app'>
"Anum :"{{Anum}} <br>
"Bnum :"{{Bnum}} <br>
"Gnum :"{{Gnum}} <br>
<button @click="test">程序测试按钮</button>
</div>
</template>
<script>
import {mapState,mapMutations,mapGetters,mapActions} from 'vuex'
export default {
name: 'app',
methods:{
...mapMutations('a',['add']),
...mapActions('a',['actadd']),
test(){
this.$store.commit('add',{number:5}) // 仅调用主模块mutations中的add
this.$store.commit('a/add',{number:5}) // 调用模块a mutations中的add
this.$store.commit('b/add',{number:5}) // 调用模块b mutations中的add
}
},
computed:{
...mapState({
name:state => state.a.name, // 取别名
Anum:state => state.a.num,
Bnum:state => state.b.num,
Gnum:state => state.num
}),
...mapGetters('a',['Aget1','Aget2','Aget3','Aget4']),
...mapGetters('b',{
B1:'Bget1' // 取别名
})
}
}
</script>
附加:getters中查看各参数的打印信息:
- state 打印结果
- getters 打印结果
- rootState 打印结果
- rootGetters 打印结果