Vuex状态管理库
基本介绍
Vuex,是一个专为Vue.js应用程序开发的状态管理模式+库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
什么情况下应该使用Vuex?
- 页面有多个需要共享的状态,引入vuex,便于维护(非父子通信);
- 缓存部分异步数据,减少后端服务的访问;
每一个 Vuex 应用的核心就是 store(仓库)。“store”基本上就是一个容器,它包含着你的应用中大部分的状态 (state)。
Vuex的状态存储是响应式的。当Vue组件从store中读取状态的时候,若store中的状态发生变化,那么相应的组件也会相应地得到高效更新;不能直接改变store中的状态,改变store中的状态的唯一途径是显式地提交(commit)mutation,这样可以方便跟踪每一个状态的变化。
上一篇文章介绍的案例中,底部导航在进入detail页面后没有隐藏,这里通过Vuex来实现进入detail页隐藏底部导航。
VOA模式
新建src/VueRouter/store/index.js
状态管理js文件
import { createStore } from "vuex";
const store = createStore({
state(){
return{
isTabbarShow:true
}
}
})
export default store
在main.js中引入并进行注册
import { createApp } from 'vue'
import './style.css'
import App from './VueRouter/App.vue'
import router from './VueRouter/router' // 导入路由
import store from './VueRouter/store' // 导入状态
import ElementPlus from 'element-plus' // 引入element plus组件库
import 'element-plus/dist/index.css' // 引入element plus样式表
let app = createApp(App)
app.use(ElementPlus) // 加载Elementpuls
app.use(router) // 注册路由插件
app.use(store) // 注册vuex状态插件
app.mount("#app")
修改Detail.vue
<template>
<div>
<button @click="plBackClick">返回</button>
detail
</div>
</template>
<script>
export default {
// 挂载前隐藏
beforeMount(){
this.$store.state.isTabbarShow = false
},
beforeUnmount(){
// 卸载前显示
this.$store.state.isTabbarShow = true
},
mounted(){
console.log('接收上一个页面传来的参数',this.$route.params.plid)
},
methods:{
plBackClick(){
this.$router.back() // 返回 this.$router.forward()
}
}
}
</script>
修改App.vue如下:
<template>
<div>
<!-- 通过插槽插入子组件路由 -->
<router-view></router-view>
<!-- 增加导航 -->
<!-- 增加v-show判断底部导航是否显示 -->
<Tabbar v-show="$store.state.isTabbarShow"></Tabbar>
</div>
</template>
<script>
import Tabbar from './components/Tabbar.vue';
export default {
components:{
Tabbar
},
mounted(){
console.log(this.$store.state.isTabbarShow)
}
}
</script>
<style>
*{
margin: 0;
padding: 0;
}
ul{
list-style: none;
}
</style>
上面的例子中是在组件中直接修改的状态,this.$store.state.isTabbarShow = false
是不推荐的方法,而是需要通过提交(commit)mutation操作。
Mutation
更改Vuex的store中的状态的唯一方法是提交mutation
。Vuex中的mutation非常类似于事件:每个mutation都有一个字符串的事件类型(type)和一个回调函数(handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受state作为第一个参数。
修改index.js
import { createStore } from "vuex";
const store = createStore({
state(){
return{
isTabbarShow:true
}
},
// 修改状态的位置
mutations:{
showTabbar(state){
state.isTabbarShow = true
},
hideTabbar(state){
state.isTabbarShow = false
}
}
})
export default store
修改Detail.vue的内容:
<template>
<div>
<button @click="plBackClick">返回</button>
detail
</div>
</template>
<script>
export default {
beforeMount(){
// this.$store.state.isTabbarShow = false
this.$store.commit("hideTabbar") // commit()的内容是在index.js中的mutations
},
beforeUnmount(){
// this.$store.state.isTabbarShow = true
this.$store.commit("showTabbar") // commit()的内容是在index.js中的mutations
},
mounted(){
console.log('接收上一个页面传来的参数',this.$route.params.plid)
},
methods:{
plBackClick(){
this.$router.back() // 返回 this.$router.forward()
}
}
}
</script>
可以向store.commit
传入额外的参数,即mutation
的载荷(payload)
增加其他参数
修改index.js
import { createStore } from "vuex";
const store = createStore({
state(){
return{
isTabbarShow:true
}
},
// 修改状态的位置
mutations:{
// showTabbar(state){
// state.isTabbarShow = true
// },
// hideTabbar(state){
// state.isTabbarShow = false
// }
changeTabbar(state, payload){
state.isTabbarShow = payload
}
}
})
export default store
修改Detail.vue
<template>
<div>
<button @click="plBackClick">返回</button>
detail
</div>
</template>
<script>
export default {
beforeMount(){
// this.$store.state.isTabbarShow = false
// this.$store.commit("hideTabbar") // commit()的内容是在index.js中的mutations
this.$store.commit("changeTabbar",false)
},
beforeUnmount(){
// this.$store.state.isTabbarShow = true
// this.$store.commit("showTabbar") // commit()的内容是在index.js中的mutations
this.$store.commit("changeTabbar",true)
},
mounted(){
console.log('接收上一个页面传来的参数',this.$route.params.plid)
},
methods:{
plBackClick(){
this.$router.back() // 返回 this.$router.forward()
}
}
}
</script>

Action
Action
类似于mutation
,不同在于:
Action
提交的是mutation
,而不是直接变更状态;Action
可以保护任意异步操作;
修改Recommend.vue,Action
通过store.dispatch
方法触发
<template>
<div>
<ul>
<li v-for="data in $store.state.recommendList" :key="data.cinemaId">
{{data.name}}
</li>
</ul>
</div>
</template>
<script>
export default{
mounted(){
if(this.$store.state.recommendList.length===0){
this.$store.dispatch("getRecommendList")
}else{
console.log("缓存")
}
}
}
</script>
<style scoped lang="scss">
ul{
li{
padding:10px;
}
}
</style>
在store/index.js中增加Action
import { createStore } from "vuex";
import axios from "axios";
const store = createStore({
state(){
return{
isTabbarShow:true,
recommendList:[]
}
},
// 修改状态的位置,同步
mutations:{
// showTabbar(state){
// state.isTabbarShow = true
// },
// hideTabbar(state){
// state.isTabbarShow = false
// }
changeTabbar(state, payload){
state.isTabbarShow = payload
},
changeRecommendList(state, payload){
state.recommendList = payload
}
},
// 同步+异步
actions:{
async getRecommendList(store){
let res = await axios({
url:"https://m.maizuo.com/gateway?cityId=110100&ticketFlag=1&k=112132",
headers:{
'X-Client-Info':'{"a":"3000","ch":"1002","v":"5.2.1","e":"16931924276063063597842433"}',
'X-Host':'mall.film-ticket.cinema.list'
}
})
// console.log(res.data.data.cinemas)
// 提交mutation
store.commit("changeRecommendList",res.data.data.cinemas)
}
}
})
export default store
Getter
在Recommend.vue中增加列表筛选功能
<template>
<div>
<select v-model="type">
<option :value="1">App订票</option>
<option :value="0">前台兑换</option>
</select>
<ul>
<li v-for="data in filterRecommendList" :key="data.cinemaId">
{{data.name}}
</li>
</ul>
</div>
</template>
<script>
export default{
data(){
return{
type:1
}
},
mounted(){
if(this.$store.state.recommendList.length===0){
this.$store.dispatch("getRecommendList")
}else{
console.log("缓存")
}
},
computed:{
filterRecommendList(){
return this.$store.state.recommendList.filter(item=>item.eTicketFlag===this.type)
}
}
}
</script>
<style scoped lang="scss">
ul{
li{
padding:10px;
}
}
</style>
这里使用到了计算属性computed
,这里将computed
的方法迁移到store/index.js中
import { createStore } from "vuex";
import axios from "axios";
const store = createStore({
state(){
return{
isTabbarShow:true,
recommendList:[]
}
},
// 修改状态的位置,同步
mutations:{
// showTabbar(state){
// state.isTabbarShow = true
// },
// hideTabbar(state){
// state.isTabbarShow = false
// }
changeTabbar(state, payload){
state.isTabbarShow = payload
},
changeRecommendList(state, payload){
state.recommendList = payload
}
},
// 同步+异步
actions:{
async getRecommendList(store){
let res = await axios({
url:"https://m.maizuo.com/gateway?cityId=110100&ticketFlag=1&k=112132",
headers:{
'X-Client-Info':'{"a":"3000","ch":"1002","v":"5.2.1","e":"16931924276063063597842433"}',
'X-Host':'mall.film-ticket.cinema.list'
}
})
// console.log(res.data.data.cinemas)
// 提交mutation
store.commit("changeRecommendList",res.data.data.cinemas)
}
},
// store的计算属性 (computed)
getters:{
filterRecommendList(state){
return (type)=>{
return state.recommendList.filter(item=>item.eTicketFlag===type)
}
}
}
})
export default store
对应修改Recommend.vue
<template>
<div>
<select v-model="type">
<option :value="1">App订票</option>
<option :value="0">前台兑换</option>
</select>
<ul>
<li v-for="data in $store.getters.filterRecommendList(type)" :key="data.cinemaId">
{{data.name}}
</li>
</ul>
</div>
</template>
<script>
export default{
data(){
return{
type:1
}
},
mounted(){
if(this.$store.state.recommendList.length===0){
this.$store.dispatch("getRecommendList")
}else{
console.log("缓存")
}
},
// computed:{
// filterRecommendList(){
// return this.$store.state.recommendList.filter(item=>item.eTicketFlag===this.type)
// }
// }
}
</script>
<style scoped lang="scss">
ul{
li{
padding:10px;
}
}
</style>
辅助函数
mapState
修改App.vue,增加computed
引入mapState
取代this.$store.state.isTabbarShow
这种复杂的写法。
<template>
<div>
<!-- 通过插槽插入子组件路由 -->
<router-view></router-view>
<!-- 增加导航 -->
<!-- 增加v-show判断 -->
<Tabbar v-show="isTabbarShow"></Tabbar>
</div>
</template>
<script>
import { mapState } from 'vuex';
import Tabbar from './components/Tabbar.vue';
export default {
components:{
Tabbar
},
mounted(){
console.log(this.$store.state.isTabbarShow)
console.log(this.$store.state.recommendList)
},
// computed:{
// isTabbarShow(){
// return this.$store.state.isTabbarShow
// }
// }
computed:{
...mapState(['isTabbarShow'])
}
}
</script>
<style>
*{
margin: 0;
padding: 0;
}
ul{
list-style: none;
}
</style>
其中的
computed:{
...mapState(['isTabbarShow'])
}
等价于
computed:{
isTabbarShow(){
return this.$store.state.isTabbarShow
}
}
同样的方式修改Recommend.vue
<template>
<div>
<select v-model="type">
<option :value="1">App订票</option>
<option :value="0">前台兑换</option>
</select>
<ul>
<li v-for="data in $store.getters.filterRecommendList(type)" :key="data.cinemaId">
{{data.name}}
</li>
</ul>
</div>
</template>
<script>
import { mapState } from 'vuex';
export default{
data(){
return{
type:1
}
},
mounted(){
// if(this.$store.state.recommendList.length===0){
if(this.recommendList.length===0){
this.$store.dispatch("getRecommendList")
}else{
console.log("缓存")
}
},
computed:{
...mapState(["recommendList"])
}
// computed:{
// filterRecommendList(){
// return this.$store.state.recommendList.filter(item=>item.eTicketFlag===this.type)
// }
// }
}
</script>
<style scoped lang="scss">
ul{
li{
padding:10px;
}
}
</style>
mapMutations
修改Detail.vue,增加computed
引入mapMutations
取代this.$store.commit("changeTabbar",false)
这种复杂的写法。
<template>
<div>
<button @click="plBackClick">返回</button>
detail
</div>
</template>
<script>
import { mapMutations } from 'vuex'
export default {
beforeMount(){
// this.$store.state.isTabbarShow = false
// this.$store.commit("hideTabbar") // commit()的内容是在index.js中的mutations
// this.$store.commit("changeTabbar",false)
this.changeTabbar(false)
},
beforeUnmount(){
// this.$store.state.isTabbarShow = true
// this.$store.commit("showTabbar") // commit()的内容是在index.js中的mutations
// this.$store.commit("changeTabbar",true)
this.changeTabbar(true)
},
mounted(){
console.log('接收上一个页面传来的参数',this.$route.params.plid)
},
methods:{
...mapMutations(['changeTabbar']),
plBackClick(){
this.$router.back() // 返回 this.$router.forward()
}
}
}
</script>
mapActions
修改Recommend.vue,增加methods
引入mapActions
取代$store.dispatch()
这种复杂的写法。
<template>
<div>
<select v-model="type">
<option :value="1">App订票</option>
<option :value="0">前台兑换</option>
</select>
<ul>
<li v-for="data in $store.getters.filterRecommendList(type)" :key="data.cinemaId">
{{data.name}}
</li>
</ul>
</div>
</template>
<script>
import { mapActions, mapState } from 'vuex';
export default{
data(){
return{
type:1
}
},
mounted(){
// if(this.$store.state.recommendList.length===0){
if(this.recommendList.length===0){
// this.$store.dispatch("getRecommendList")
this.getRecommendList()
}else{
console.log("缓存")
}
},
methods:{
...mapActions(["getRecommendList"])
},
computed:{
...mapState(["recommendList"])
}
// computed:{
// filterRecommendList(){
// return this.$store.state.recommendList.filter(item=>item.eTicketFlag===this.type)
// }
// }
}
</script>
<style scoped lang="scss">
ul{
li{
padding:10px;
}
}
</style>
mapGetters
修改Recommend.vue,增加computed
引入mapGetters
取代$store.getters.filterRecommendList(type)
这种复杂的写法。
<template>
<div>
<select v-model="type">
<option :value="1">App订票</option>
<option :value="0">前台兑换</option>
</select>
<ul>
<!-- <li v-for="data in $store.getters.filterRecommendList(type)" :key="data.cinemaId"> -->
<li v-for="data in filterRecommendList(type)" :key="data.cinemaId">
{{data.name}}
</li>
</ul>
</div>
</template>
<script>
import { mapActions, mapGetters, mapState } from 'vuex';
export default{
data(){
return{
type:1
}
},
mounted(){
// if(this.$store.state.recommendList.length===0){
if(this.recommendList.length===0){
// this.$store.dispatch("getRecommendList")
this.getRecommendList()
}else{
console.log("缓存")
}
},
methods:{
...mapActions(["getRecommendList"])
},
computed:{
...mapState(["recommendList"]),
...mapGetters(["filterRecommendList"])
}
// computed:{
// filterRecommendList(){
// return this.$store.state.recommendList.filter(item=>item.eTicketFlag===this.type)
// }
// }
}
</script>
<style scoped lang="scss">
ul{
li{
padding:10px;
}
}
</style>
Module
当应用比较庞大时,store对象就有可能变得很臃肿。为了解决这个问题,Vuex允许将store分割成模块(module)。每个模块拥有自己的state、mutation、action、getter等,甚至是嵌套子模块。
可以看到示例中store/index.js中定义了两个状态isTabbarShow
和recommendList
,这里分割成两个module
const store = createStore({
state(){
return{
isTabbarShow:true,
recommendList:[]
}
}
在src/VueRouter/store/下新建module目录,分别创建RecommendModule.js和TabbarModule.js,并在这两个文件中开启命名空间
RecommendModule.js
import axios from "axios";
const RecommendModule = {
namespaced: true, // 开启命名空间
state(){
return{
recommendList:[]
}
},
mutations:{
changeRecommendList(state, payload){
state.recommendList = payload
}
},
actions:{
async getRecommendList(store){
let res = await axios({
url:"https://m.maizuo.com/gateway?cityId=110100&ticketFlag=1&k=112132",
headers:{
'X-Client-Info':'{"a":"3000","ch":"1002","v":"5.2.1","e":"16931924276063063597842433"}',
'X-Host':'mall.film-ticket.cinema.list'
}
})
// console.log(res.data.data.cinemas)
// 提交mutation
store.commit("changeRecommendList",res.data.data.cinemas)
}
},
// store的计算属性 (computed)
getters:{
filterRecommendList(state){
return (type)=>{
return state.recommendList.filter(item=>item.eTicketFlag===type)
}
}
}
}
export default RecommendModule
TabbarModule.js
const TabbarModule = {
namespaced: true, // 开启命名空间
state() {
return {
isTabbarShow: true,
}
},
mutations:{
changeTabbar(state, payload){
state.isTabbarShow = payload
}
}
}
export default TabbarModule
修改index.js
import { createStore } from "vuex";
import TabbarModule from "./module/TabbarModule"
import RecommendModule from "./module/RecommendModule"
const store = createStore({
modules:{
TabbarModule,
RecommendModule
}
})
export default store
修改App.vue
<template>
<div>
<!-- 通过插槽插入子组件路由 -->
<router-view></router-view>
<!-- 增加导航 -->
<!-- 增加v-show判断 -->
<Tabbar v-show="isTabbarShow"></Tabbar>
</div>
</template>
<script>
import { mapState } from 'vuex';
import Tabbar from './components/Tabbar.vue';
export default {
components:{
Tabbar
},
mounted(){
console.log(this.$store.state.TabbarModule.isTabbarShow)
console.log(this.$store.state.RecommendModule.recommendList)
},
// computed:{
// isTabbarShow(){
// return this.$store.state.isTabbarShow
// }
// }
computed:{
...mapState('TabbarModule',['isTabbarShow'])
}
}
</script>
<style>
*{
margin: 0;
padding: 0;
}
ul{
list-style: none;
}
</style>
修改Recommend.vue
<template>
<div>
<select v-model="type">
<option :value="1">App订票</option>
<option :value="0">前台兑换</option>
</select>
<ul>
<!-- <li v-for="data in $store.getters.filterRecommendList(type)" :key="data.cinemaId"> -->
<li v-for="data in filterRecommendList(type)" :key="data.cinemaId">
{{data.name}}
</li>
</ul>
</div>
</template>
<script>
import { mapActions, mapGetters, mapState } from 'vuex';
export default{
data(){
return{
type:1
}
},
mounted(){
// if(this.$store.state.recommendList.length===0){
if(this.recommendList.length===0){
// this.$store.dispatch("getRecommendList")
this.getRecommendList()
}else{
console.log("缓存")
}
},
methods:{
...mapActions('RecommendModule',["getRecommendList"])
},
computed:{
...mapState('RecommendModule',["recommendList"]),
...mapGetters('RecommendModule',["filterRecommendList"])
}
// computed:{
// filterRecommendList(){
// return this.$store.state.recommendList.filter(item=>item.eTicketFlag===this.type)
// }
// }
}
</script>
<style scoped lang="scss">
ul{
li{
padding:10px;
}
}
</style>
VCA下的Vuex
由于组合式API中不能使用this
,所有类似this.$store
在VCA下可以通过调用useStore
函数来实现,与this.$store
等效。
App.vue
<template>
<div>
<!-- 通过插槽插入子组件路由 -->
<router-view></router-view>
<!-- 增加导航 -->
<Tabbar v-show="store.state.TabbarModule.isTabbarShow"></Tabbar>
<!-- <Tabbar></Tabbar> -->
</div>
</template>
<script setup>
import Tabbar from './components/Tabbar.vue';
import { useStore } from 'vuex';
const store = useStore() // 相当于this.$store
// export default {
// components:{
// Tabbar
// }
// }
</script>
<style>
*{
margin: 0;
padding: 0;
}
ul{
list-style: none;
}
</style>
Detail.vue
<template>
<div>
<button @click="plBackClick">返回</button>
detail
</div>
</template>
<script setup>
import {onMounted, onBeforeMount,onBeforeUnmount} from 'vue'
import { useRoute,useRouter } from 'vue-router'
import { storeKey, useStore } from 'vuex';
const route = useRoute()
const router = useRouter()
const store = useStore()
onBeforeMount(()=>{
store.commit("TabbarModule/changeTabbar", false)
})
onBeforeUnmount(()=>{
store.commit("TabbarModule/changeTabbar", true)
})
onMounted(()=>{
console.log('接收上一个页面传来的参数',route.params.plid)
})
const plBackClick = ()=>{
router.back() // 返回 router.forward()
}
</script>
Recommend.vue
<template>
<div>
<select v-model="type">
<option :value="1">App订票</option>
<option :value="0">前台兑换</option>
</select>
<ul>
<li v-for="data in store.getters['RecommendModule/filterRecommendList'](type)" :key="data.cinemaId">
{{data.name}}
</li>
</ul>
</div>
</template>
<script setup>
import { onMounted, ref } from 'vue'
import { useStore } from 'vuex'
const type =ref(1)
const store = useStore()
onMounted(()=>{
if(store.state.RecommendModule.recommendList.length===0){
// store.getRecommendList()
store.dispatch("RecommendModule/getRecommendList")
}else{
console.log("缓存")
}
})
</script>
<style scoped lang="scss">
ul{
li{
padding:10px;
}
}
</style>
Vuex持久化插件
vuex可以进行全局的状态管理,但刷新后数据就会丢失掉,而有时候我们需要进行本地存储而使数据状态持久化。这里使用vuex-persistedstate
来实现存储到localStorage中。
npm install --save vuex-persistedstate
修改src/VueRouter/store/index.js
import { createStore } from "vuex";
import TabbarModule from "./module/TabbarModule"
import RecommendModule from "./module/RecommendModule"
import createPersistedState from 'vuex-persistedstate'
const store = createStore({
// plugins:[createPersistedState()], // 全部保存
plugins:[createPersistedState({
// reducer:(state)=>state.TabbarModule.isTabbarShow //只保存isTabbarShow的状态
reducer:(state)=>{
return {
isTabbarShow:state.TabbarModule.isTabbarShow
}
}
})],
modules:{
TabbarModule,
RecommendModule
}
})
export default store
