Vuex 状态管理初级知识

1,174 阅读6分钟

官方解释: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插件

插件配置

  1. 下载 vuex 插件
npm install vuex --save
  1. 在src目录下创建一个store目录,并在目录下创建index.js文件
  2. 编写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
  1. 编写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中查看各参数的打印信息:

  1. state 打印结果

state打印结果

  1. getters 打印结果

getters打印结果

  1. rootState 打印结果

rootState打印结果

  1. rootGetters 打印结果

rootGetters打印结果