闲话Vuex
前言:为什么要Vuex
在Vue项目中, 功能组件和服务组件的封装都会有各种各样的数据传递,用 props 定义字段或者是子组件 emit 来通信。但是,当我们的项目的复杂度逐渐增长的时候,组件会越来越多,而且一些组件并不存在调用关系,一些数据需要共享的时候,那么问题就来了:
传参的方法对于多层嵌套的组件将会非常繁琐,并且对于兄弟组件间的状态传递无能为力;
采用父子组件直接引用或者通过事件来变更和同步状态的多份拷贝,代码冗余,慢慢的难以维护。
所以我们在项目中经常是全局定义一个实例“全局单例模式”,当然你也可以定义多个实例来共享一个数据源。
比如说我们有一个系统,包含左侧菜单组件以及右侧内容区域组件两部分。现在需要在内容区域给系统做一个全屏的功能,即点击右侧按钮时菜单隐藏。如果我们不使用Vuex,首先我们要在内容组件中使用 $emit 改变父组件的内容,然后将改变后的值传递左侧菜单组件控制隐藏。而现在我们有了 Vuex,菜单组件和内容组件共享某个状态变量,需要隐藏时只需要在内容组件中改变状态,即可在菜单组件中共享到相应的变量。
Vuex 就是为了解决这些问题而生。
Vue核心
state:驱动应用的数据源,类似于 vue 中的 data,是一棵单一状态树,涉及到的状态变量都存放在 state 上。
getter:store 的计算属性。类似于 vue 中的 computed 计算属性。当有多个组件对某个状态引用后过滤时,需要在多个组件内部使用 computed 进行计算,但有了 Getter 后我们可以将这个计算提前到 Vuex 中,一次计算多处使用。例如下面的代码我们要过滤出启用(状态为 1)状态的数据:
mutation:类似于 vue 中的方法,当我们需要改变 Vuex 中的某个变量时需要用到。
例如我们前面提到的显示隐藏菜单:
一般 mutations 中方法接受的第一个参数为 state,后面的参数则是传递的值。例如我们需要显示菜单则在组件中调用:
this.$store.commit('onCollapse', true);
这里改变值需要通过 commit(‘方法名’, ’…参数’)调用。
Action: 类似于 mutation,但是改变状态时只能通过提交 mutation,而不能直接改变。同时 action 可以包含异步的操作, 而 mutation 是不允许包含异步操作的。
Vuex 模块分割
由于 state 使用的是单一状态树,在项目较大时会导致 Vuex 变得臃肿且不易维护,这时候我们需要对项目进行分模块化处理,每一个模块都有自己的 state、mutation、action、getter:
Modules
- app.js
- user.js
index.js
最后统一在 inde.js 中导出:
import Vue from 'vue'
import vuex from 'vuex'
Vue.use(vuex);
import app from './modules/app'
import user from './modules/user'
const store = new vuex.Store({
modules: {
app: app,
user: user
}
})
export default store
Vuex 辅助函数
当我们的项目调用比较简单时辅助函数可能不会起到很大的作用,但当项目调用复杂时,这些辅助函数(mapState、mapGetters、mapMutations、mapActions)能大大提高开发效率,使得代码更加简洁优雅。
其中 mapState、mapGetters 是对变量的获取,放在组件的 computed 计算属性中即可,如下:
computed: {
...mapState({
themeColor: state => state.app.themeColor,
sysName: state => state.app.appName,
appHomeRouter: state => state.app.appHomeRouter,
})
}
而 mapMutations、mapActions 是对方法的调用,需要放在组件的 method 中,如下:
methods: {
...mapMutations({
changeStatus: 'changeStatus'
}),
changeStatus(){
this.changeStatus()//直接调用 mapMutations 中的方法
// this.$store.commit('changeStatus')//不使用辅助函数时的调用
},
...mapActions({
numAdd: 'actionNumAdd'
}),
actionnum6(){
this.numAdd()//直接调用 mapActions 中的方法
// this.$store.dispatch('actionNumAdd')//不使用辅助函数时的调用
},
}
情景案例
有时候我们终于完成了一份试卷,可能由于手滑不小心把浏览器给关闭或者刷新了一下,导致辛辛苦苦做完的题目又得重头来过,而 Vuex 结合 Map 同样可以解决这个问题,核心思想就是将已经做过的题目更新到 Vuex 中。
讲到这里肯定会有人存在疑问:Vuex 中存储的状态变量在浏览器关闭或者刷新时会重新初始,这样如何能在浏览器刷新的时候获取到存储的值呢?这里我们需要引进缓存——sessionStorage。
首先在 state 中定义一个 answerMap:
state: {
answerMap: sessionStorage.getItem("answerMap")
}
然后在每次做题完成后将答案存储到 sessionStorage 中,即:
const mutations = {
setAnswerMap(state, answer) {
let map = JSON.parse(state.answerMap);
if (map == null || !(map instanceof Map)) {
map = new Map();
}
map.set(answer.id, answer);
sessionStorage.setItem("answerMap", JSON.stringify(map))
},
}
正常情况下到这里就结束了,但是实际操作后会发现每次获取到的 answerMap 总是为 null,原因在于最后一行代码:
sessionStorage.setItem("answerMap", JSON.stringify(map))
不能直接将 map 通过 JSON.stringify() 进行转换,需要先将 map 转成 obj 对象后再转成 json 对象,将 map 转成 obj:
let obj = Object.create(null);
for (let [k, v] of map) {
obj[k] = v;
}
此时在获取则解决为 null 的情况,完善后代码如下:
const mutations = {
setAnswerMap(state, answer) {
let map = JSON.parse(state.answerMap);
if (map == null || !(map instanceof Map)) {
map = new Map();
}
map.set(answer.id, answer);
let obj = Object.create(null);
for (let [k, v] of map) {
obj[k] = v;
}
sessionStorage.setItem("answerMap", JSON.stringify(obj))
},
}
这里借助了 Vuex + map + sessionStorage 防止已做题目丢失的情况,当然只是提供一种解决问题的思路,可能在真实场景中会更加复杂,需要根据不同的场景灵活运用。比如说我们如果需要在手动清除浏览器缓存、甚至服务器奔溃后依旧能获取到已做的题目则使用 Vuex 明显不合适,此时就需要利用 Redis 缓存来处理。
在实际项目中可能会遇到更加复杂的情况,但是万变不离其宗,在掌握基础的情况下总会有一种思路能够更好的解决问题。
- 点赞
- 收藏
- 关注作者
评论(0)