Vuex
什么是Vuex?
概念:专门在Vue中实现集中式状态(数据)管理的一个Vue插件,对应用中多个组件的共享状态进行集中式管理(读/写),也是组件间通信的方式,且适用于任意组件间通信
之前想要传递数据,可以使用全局事件总线/消息订阅去实现,但是如果有很多组件都想要去读和写 某个组件中的数据,那么需要在每个组件中去绑定或触发 读/写的事件,一旦组件数过多代码就会变得很复杂,所以可以使用vuex去实现这个目的。
Vuex 工作原理
在Vuex中有三个对象 Actions、Mutations、State
State 里面保存的是 数据,以key-value 的形式保存
一个动作/函数 的执行流程:
-
在Vue 组件中 点击了加1按钮,想让一个数字加1,但这个数字不在该组件自身,在Vuex的state里
-
调用dispatch() 第一个参数是 要进行的动作类型,比如是”add“,第二个参数是 要加几
-
调用了dispatch后,你执行的动作和值就来到了 Actions,Actions也是一个Object对象,里面一定会有一个“ add”,它的值是一个函数,当你传进来的 “add” 能和 Actions中的 "add" 对上,就会引起 这个函数的调用,这个函数一旦被调用就受到了传进来的 数值
-
在 ”add“ 对应的函数中自己去调用 commit 函数 ,commit 函数 和 dispatch 函数的参数相同
-
执行完 commit 函数后,就走到了 Mutations 对象中,它也有 ”add“ ,值也是 函数,在这个函数中 会拿到两个东西,第一个是 state 第二个是你传进来的数据,修改state 中的数据,底层会自动调用 Mutate,就会引起state 中数据的变化
-
然后重新解析组件,渲染
在Action 中 发送 Ajax请求,当你不需要发送Ajax请求时,可以直接调用 commit 函数
上述三个对象 Actions、Mutations、State都是由Store管理的 ,调用也是通过store
搭建Vuex环境
1.安装Vuex
npm i vuex@3
这里我们需要安装vuex3版本,因为 vuex4只能在 vue3中使用
2.Vue.use(Vuex)
这里的注意点是 在 使用 Vue.use(Vuex) 必须在创建store 实例之前,所以需要先创建store实例
按照官网的写法,是在src目录下创建一个 store目录,在该目录下创建 index.js,在该文件中创建 store实例和使用 vuex插件
//该文件用于创建Vuex中最核心的Store
//引入Vue
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//使用vuex 插件
Vue.use(Vuex)
//准备Actions--用于响应组件中的动作
const actions = {}
//准备 mutations--用于操作数据
const mutations = {}
//准备state--用于存储数据
const state = {}
//创建并暴露 store
export default new Vuex.Store({
actions,
mutations,
state
})
3.在 main.js 引入 store,并传入 vm 中
import Vue from "vue";
import App from './App'
import store from './store/index'
new Vue({
el:"#app",
render:h => h(App),
store,
beforeCreate(){
Vue.prototype.$bus = this
}
})
修改 计数案例
在没有使用vuex 的时候的计数案例:
<template>
<div>
<h1>当前的值为:{{totalNum}}</h1>
<select v-model="selectNum">
<option :value="1">1</option>
<option :value="2">2</option>
<option :value="3">3</option>
</select>
<button @click="addNum">+</button>
<button @click="decrementNum">-</button>
<button @click="addNumOdd">奇数加</button>
<button @click="addWait">等会加</button>
</div>
</template>
<script>
export default {
name:'Counts',
data(){
return{
totalNum:0,
selectNum:1
}
},
methods:{
addNum(){
this.totalNum += this.selectNum
},
decrementNum(){
this.totalNum -= this.selectNum
},
addNumOdd(){
if(this.totalNum % 2){
this.totalNum+=this.selectNum
}
},
addWait(){
setTimeout(() => {
this.totalNum+=this.selectNum
},500)
}
}
}
</script>
<style scoped>
button{
margin-left: 5px;
}
</style>
使用 Vuex来修改计数案例
按照Vuex的执行流程
-
首先把 定义在 Counts组件中的数据 放到 store 的 state 对象中
const state = { totalNum:0 }
-
在 Counts 组件的方法中调用 dispatch
methods:{ addNum(){ this.$store.dispatch("addNumber",this.selectNum) }, decrementNum(){ this.$store.dispatch("decrement",this.selectNum) }, addNumOdd(){ if(this.$store.state.totalNum % 2){ this.$store.dispatch("addNumber",this.selectNum) } }, addWait(){ setTimeout(() => { this.$store.dispatch("addNumber",this.selectNum) },500) } }
-
在 store的 actions 和 mutations 中 定义 dispatch 传入的方法
const actions = { addNumber(context,value){ context.commit("ADDNUMBER",value) }, decrement(context,value){ context.commit("DECREMENT",value) } } //准备 mutations--用于操作数据 const mutations = { ADDNUMBER(state,value){ state.totalNum += value }, DECREMENT(state,value){ state.totalNum -= value }, }
-
在之前的模板中 的差值语法不能直接使用 totalNum,而是 用 $store.state.totalNum
如果没有网络请求或其他业务逻辑,组件中 也可以越过actions,即不写dispatch,直接写commit
store 中的getters 配置项
1.当state中的数据需要经过加工后再使用,并且这个加工方式需要多次复用,可以在store 中配置 getters
2.getters 中计算出来的属性,用函数表示,参数是 state,还要把getters 传给Store
const getters = {
bigSum(state){
return state.sum * 10
}
}
export default new Vuex.Store({
...
getters
})
3.在组件中使用 $store.getters.bigSum 来获取这个值
四个map方法的使用
当我们在组件模板中去使用 state 中的数据时,每次都需要写 $store.state.xxx
我们就可以把 xxx 作为计算属性,“$store.state" 从计算属性中传过去,这样在模板中就简化了代码
但是 这样还会产生很多的计算属性,那么这时候就可以借助 vue提供的方法生成计算属性
-
mapState:帮助我们映射state 中的数据为计算属性
computed:{ //第一种写法:对象写法 ...mapState({sum:'sum',school:'school',subject:'subject'}) //第二种写法:数组写法 ...mapState(['sum','school','subject']) }
需要注意 数组写法,必须保证计算属性的名字和 你想要在state中拿到数据的名字一致
mapState 前的 "..." 的含义是 把 这个方法里面的对象追加到 computed中
-
mapGetters:帮助我们映射getters 中的数据为计算属性
使用方法和 mapState 相同,需要注意的是这两个方法需要从vuex 中用 import引入
-
mapActions:用于帮助我们生成与actions对话的方法,即包含$store.dispatch(xxx) 的函数
methods:{ //第一种写法,对象写法 ...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'}) //第二种写法:数组写法 ...mapAction(['jiaOdd','jiaWait']) }
-
mapMutation:用于帮助我们生成与 mutations对话的方法,即包含 $.store.commit(xxx) 函数
使用方法和 mapActions 相同
注意:mapActions和 mapMutation 使用时,若需要传递参数,在模板中绑定事件时传递好参数,否则参数就是事件对象
下面是案例:
<template>
<div>
<!-- <h1>当前的值为:{{$store.state.totalNum}}</h1> -->
<!-- <h3>当前的值放大十倍为:{{$store.getters.bigNum}}</h3> -->
<h1>当前的值为:{{totalNum}}</h1>
<h3>当前的值放大十倍为:{{bigNum}}</h3>
<select v-model="selectNum">
<option :value="1">1</option>
<option :value="2">2</option>
<option :value="3">3</option>
</select>
<!-- <button @click="addNum">+</button>
<button @click="decrementNum">-</button> -->
<!-- 调用方法时需要传参,否则参数就是鼠标点击事件-->
<button @click="ADDNUMBER(selectNum)">+</button>
<button @click="DECREMENT(selectNum)">-</button>
<button @click="addNumOdd(selectNum)">奇数加</button>
<button @click="addWait(selectNum)">等会加</button>
</div>
</template>
<script>
import {mapState,mapGetters,mapActions,mapMutations} from 'vuex'
export default {
name:'Counts',
data(){
return{
selectNum:1
}
},
computed:{
...mapState(['totalNum']),
...mapGetters(['bigNum'])
},
methods:{
//这里用的都是 数组的方式,要求传入的名字要和store中 Actions和Mutations中的名字一致,使用时也是用这个名字
...mapActions(['addNumOdd','addWait']),
...mapMutations(['ADDNUMBER','DECREMENT'])
}
}
</script>
<style scoped>
button{
margin-left: 5px;
}
</style>
//准备Actions--用于响应组件中的动作
const actions = {
addNumOdd(context,value){
if(context.state.totalNum % 2){
context.commit("ADDNUMBER",value)
}
},
addWait(context,value){
setTimeout(() => {
context.commit("ADDNUMBER",value)
},500)
}
}
//准备 mutations--用于操作数据
const mutations = {
ADDNUMBER(state,value){
state.totalNum += value
},
DECREMENT(state,value){
state.totalNum -= value
},
}
//准备state--用于存储数据
const state = {
totalNum:0
}
//准备getters--用于计算state中的数据
const getters = {
bigNum(){
return state.totalNum * 10
}
}
Vuex模块化
目的:让代码更好维护,让多种数据分类更加明确
上面的案例只有一个组件,现在我们再创建一个组件,体现出多组件共享数据
<template>
<div>
<h1>人员列表</h1>
<h3>列表中第一个人是:{{firstPerson.name}}</h3>
<h3 style="color-red">Counts组件计算后的值:{{counts}}</h3>
<input type="text" v-model="personName">
<button @click="addPerson">添加</button>
<button @click="addPersonWang">添加姓王的人</button>
<ul>
<li v-for="person in personList" :key="person.id">{{person.name}}</li>
</ul>
</div>
</template>
<script>
import {nanoid} from 'nanoid'
export default {
name:'Persons',
data(){
return{
personName:''
}
},
computed:{
personList(){
return this.$store.state.personList
},
firstPerson(){
return this.$store.getters.firstPerson
},
counts(){
return this.$store.state.totalNum
}
},
methods:{
addPerson(){
const person = {id:nanoid(),name:this.personName}
this.$store.commit('ADD_PERSON',person)
this.personName = ''
},
addPersonWang(){
const person = {id:nanoid(),name:this.personName}
this.$store.dispatch('addPersonWang',person)
this.personName = ''
}
}
}
</script>
<style scoped>
button{
margin-left: 5px;
}
</style>
//该文件用于创建Vuex中最核心的Store
//引入Vue
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//使用vuex 插件
Vue.use(Vuex)
//准备Actions--用于响应组件中的动作
const actions = {
// addNumber(context,value){
// context.commit("ADDNUMBER",value)
// },
// decrement(context,value){
// context.commit("DECREMENT",value)
// }
addNumOdd(context,value){
if(context.state.totalNum % 2){
context.commit("ADDNUMBER",value)
}
},
addWait(context,value){
setTimeout(() => {
context.commit("ADDNUMBER",value)
},500)
},
addPersonWang(context,value){
if(value.name.indexOf('王') === 0){
context.commit('ADD_PERSON',value)
}else{
alert("请输入姓王的人")
}
}
}
//准备 mutations--用于操作数据
const mutations = {
ADDNUMBER(state,value){
state.totalNum += value
},
DECREMENT(state,value){
state.totalNum -= value
},
ADD_PERSON(state,value){
state.personList.unshift(value)
}
}
//准备state--用于存储数据
const state = {
totalNum:0,
personList:[{id:"001",name:"张三"}]
}
//准备getters--用于计算state中的数据
const getters = {
bigNum(){
return state.totalNum * 10
},
firstPerson(){
return state.personList[0]
}
}
//创建并暴露 store
export default new Vuex.Store({
actions,
mutations,
state,
getters
})
新添加了一个 Persons组件,我们发现 store中的代码变得越来越多,所以我们可以把它们拆成两个store,把actions、mutations、state、getters 拆分成两部分,可以继续写在 index.js中,也可以分开写在两个文件中,在index.js 中引入这两个文件
count.js 如下:
const actions = {
addNumOdd(context,value){
if(context.state.totalNum % 2){
context.commit("ADDNUMBER",value)
}
},
addWait(context,value){
setTimeout(() => {
context.commit("ADDNUMBER",value)
},500)
},
}
const mutations = {
ADDNUMBER(state,value){
state.totalNum += value
},
DECREMENT(state,value){
state.totalNum -= value
},
}
const state = {
totalNum:0
}
const getters = {
bigNum(){
return state.totalNum * 10
},
}
export default {
namespaced:true,
actions,
mutations,
state,getters
}
person.js 如下
const actions = {
addPersonWang(context,value){
if(value.name.indexOf('王') === 0){
context.commit('ADD_PERSON',value)
}else{
alert("请输入姓王的人")
}
}
}
const mutations = {
ADD_PERSON(state,value){
state.personList.unshift(value)
}
}
const state = {
personList:[{id:"001",name:"张三"}]
}
const getters = {
firstPerson(){
return state.personList[0]
}
}
export default {
namespaced:true,
actions,
mutations,
state,getters
}
在这两个新的js文件中,可以看到多了一个东西是 namespaced,就是命名空间,设置值为true。默认该值是false,如果不设置为 true,在组件中传入的配置名是识别不到的
在 index.js中
//该文件用于创建Vuex中最核心的Store
//引入Vue
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//使用vuex 插件
Vue.use(Vuex)
import countAbout from './count'
import personAbout from './person'
//创建并暴露 store
export default new Vuex.Store({
modules:{
countAbout,
personAbout
}
})
引入js文件,在 创建Store 实例时,在配置项配置 modules,里面的值是 引入的对象
在组件中使用分为两种情况
-
使用mapxxx方法生成
<script> import {mapState,mapGetters,mapActions,mapMutations} from 'vuex' export default { name:'Counts', data(){ return{ selectNum:1 } }, computed:{ ...mapState('countAbout',['totalNum']), ...mapGetters('countAbout',['bigNum']) }, methods:{ ...mapActions('countAbout',['addNumOdd','addWait']), ...mapMutations('countAbout',['ADDNUMBER','DECREMENT']) } } </script>
在引用 State、Getters、Actions、Mutations时,前面要传入一个参数,就是你在创建Store时配置的对象名,如果namespaced设置为false,那么这里传入的参数就识别不到,在 模板中只能用countAbout.xxx去使用数据了
-
使用 this.$store.xxx
<script> import {nanoid} from 'nanoid' export default { name:'Persons', data(){ return{ personName:'' } }, computed:{ personList(){ return this.$store.state.personAbout.personList }, firstPerson(){ return this.$store.getters['personAbout/firstPerson'] }, counts(){ return this.$store.state.countAbout.totalNum } }, methods:{ addPerson(){ const person = {id:nanoid(),name:this.personName} this.$store.commit('personAbout/ADD_PERSON',person) this.personName = '' }, addPersonWang(){ const person = {id:nanoid(),name:this.personName} this.$store.dispatch('personAbout/addPersonWang',person) this.personName = '' } } } </script>
-
引入 state 时,使用 this.$store.state.personAbout.personList
-
引入getters 时,使用 this.$store.getters['personAbout/firstPerson']
-
调用dispatch时,this.$store.dispatch('personAbout/addPersonWang',person)
-
调用commit时,this.$store.commit('personAbout/ADD_PERSON',person)
getters、dispatch、commit 都需要使用 namespaced/xxx的方式
-