Vue3 直接使用Vuex的mapState和mapGetters时报错的分析及解决方案
Vue3 组合式(setup)关于mapState和mapGetters直接使用会因为computed中没有this而报错,故而对其重新封装,给每个mapState和mapGetters返回的每个函数都绑定一个store对象
Vuex 提供了 mapState、mapGetters、mapActions 和 mapMutations 四个函数,其返回结果分别是 mappedState,mappedGetter,mappedAction 和 mappedAction 四个函数。
使用方式如下 store/index.js
import { createStore } from 'vuex'
const state = {
msg: 'hello world'
}
const getters = {
newMsg: state => `Hi~ ${state.msg}`
}
export default createStore({
state, getters
})
一、获取 state 中 msg 和 getters 中 newMsg 的方法
1、直接获取
<script setup>
import { useStore } from 'vuex'
const $store = useStore()
console.log($store.state.msg) // 'hello world'
console.log($store.getters.newMsg) // 'Hi~ hello world'
</script>
2、计算属性方式获取
<script setup>
import { computed } from 'vue'
import { useStore } from 'vuex'
const $store = useStore()
// 计算属性返回的 msg 是一个 ComputedRefImpl 引用对象
// 模板以外的地方使用时,需要 ".value"
const msg = computed(() => $store.state.msg)
console.log(msg.value) // 'hello world'
// 计算属性返回的 newMsg 是一个 ComputedRefImpl 引用对象
// 模板以外的地方使用时,需要 ".value"
const newMsg = computed(() => $store.getters.newMsg)
console.log(newMsg.value) // 'Hi~ hello world'
</script>
3、使用 mapState 和 maGetters 方式获取
vue3 组件(选项式风格)
import { useStore, mapState, mapGetters } from 'vuex'
...
setup() {
const $store = useStore()
// mapState 返回的 msg 是一个 mappedState 函数
let { msg } = mapState(['msg'])
// 计算属性返回的 msg 是一个 ComputedRefImpl 引用对象
msg = computed(msg)
console.log(msg.value) // 报错
// mapGetters 返回的 newMsg 是一个 mappedGetter 函数
let { newMsg } = mapGetters(['newMsg'])
// 计算属性返回的 newMsg 是一个 ComputedRefImpl 引用对象
newMsg = computed(newMsg)
console.log(newMsg.value) // 报错
return {
msg, newMsg
}
}
...
vue3 组件(组合式风格)
<script setup>
import { computed } from 'vue'
import { useStore, mapState, mapGetters } from 'vuex'
const $store = useStore()
// mapState 返回的 msg 是一个 mappedState 函数
let { msg } = mapState(['msg'])
// 计算属性返回的 msg 是一个 ComputedRefImpl 引用对象
msg = computed(msg)
console.log(msg.value) // 报错
// mapGetters 返回的 newMsg 是一个 mappedGetter 函数
let { newMsg } = mapGetters(['newMsg'])
// 计算属性返回的 newMsg 是一个 ComputedRefImpl 引用对象
newMsg = computed(newMsg)
console.log(newMsg.value) // 报错
</script>
上述代码在执行的时候,之所以报错,是因为 Vue3 的 computed 函数中没有 this,直接通过 computed 函数去执行 mapState 和 mapGetters 返回的结果 msg 和 newMsg 以上两个方法会报错,报错内容分别为:
报错! Cannot read properties of undefined (reading 'state') at ReactiveEffect.mappedState [as fn]
报错! Cannot read properties of undefined (reading 'getters') at ReactiveEffect.mappedGetter [as fn]
原因就是因为 Vue3 的 computed 函数里没有 this,导致最后在 "msg.value" 获取值时 mappedState 函数里的 this.$store.state 是在一个 undefined 身上去读取属性,所以报错。mapState 和 mapGetters 的报错原因一致。
二、解决方法如下
1、mapState 方式,通过给 mapState 函数返回的结果 mappedState 函数 bind 绑定一个 {$store} 对象,使得其函数内的 this.$store = $store,而不是 undefined,而 $store = userStore(),是一个变量。
注* {$store} 是 {'$store': $store} 的简写,this.$store === {'$store': $store}.$store === $store
<script setup>
import { computed } from 'vue'
import { useStore, mapState } from 'vuex'
const $store = useStore()
// mapState 返回的 msg 是一个 mappedState 函数
let { msg } = mapState(['msg'])
// 计算属性返回的 msg 是一个 ComputedRefImpl 引用对象
msg = computed(msg.bind({$store}))
console.log(msg.value) // 'hello world'
</script>
2、magGetters
<script setup>
import { computed } from 'vue'
import { useStore, mapGetters } from 'vuex'
const $store = useStore()
// mapGetters 返回的 newMsg 是一个 mappedGetter 函数
let { newMsg } = mapGetters (['newMsg'])
// 计算属性返回的 newMsg 是一个 ComputedRefImpl 引用对象
newMsg = computed(newMsg.bind({$store}))
console.log(newMsg.value) // 'Hi~ hello world'
</script>
但是每次使用 mapState 和 mapGetters 都要去绑定一个 store 对象,这很不方便,所以,自己改造封装一个 mapState 和 mapGetters,名字改成大写的 MAPSTAGE 和 MAPGETTERS。
开始封装,准备一个脚本文件
store/mapStoreReset.js,除了全局模式,还兼容 modules 模块的获取方式。
import { computed } from 'vue'
import { useStore, mapState, mapGetters } from 'vuex'
// mapActions, mapMutations 组件中可以直接使用原生写法,不需要封装
/**
* 重新封装 mapState,使用方法与返回结果跟官方原生 mapState 一致,适用全局和 modules 局部
* 推荐使用数组形式获取,写法更简洁
*/
const MAPSTATE = (...args) => {
const $store = useStore()
const methodName = 'MAPSTATE'
const target = 'state'
let modulesName = null
let params = null
let storeState = {}
// 检测参数在 state 中是否存在
const paramsCheckFn = (paramsValusArgs, $storeState) => {
paramsValusArgs.forEach(item => {
if (!Object.keys($storeState).includes(item)) {
throw new Error(`${methodName}方法里的参数"${item}" Vuex ${target} 中不存在`)
}
})
}
// 调用 mapState 的参数为对象
const useObjectTypeFn = (params, $storeState) => {
if (Object.prototype.toString.call(params) === '[object Object]') {
const paramsValusArgs = Object.values(params)
paramsCheckFn(paramsValusArgs, $storeState)
}
}
// 调用 mapState 的参数为数组
const useArrayTypeFn = (params, $storeState) => {
if (Array.isArray(params)) {
if (!params.length) throw new Error('${methodName}方法的数组参数长度不能为0')
paramsCheckFn(params, $storeState)
}
}
// computed 优化,给 mapState 返回的每个函数的 this 绑定 {$store} 对象
const improveComputedFn = storeStateFnsObj => {
Object.keys(storeStateFnsObj).forEach(key => {
const fn = storeStateFnsObj[key].bind({$store})
storeState[key] = computed(fn)
})
}
if (args && args.length === 1) {
// 用户调用全局 Vuex
params = args[0]
useObjectTypeFn(params, $store.state)
useArrayTypeFn(params, $store.state)
improveComputedFn(mapState(params))
} else if (args && args.length === 2) {
// 用户调用局部模块化 Vuex
modulesName = args[0]
params = args[1]
useObjectTypeFn(params, $store.state[modulesName])
useArrayTypeFn(params, $store.state[modulesName])
improveComputedFn(mapState(modulesName, params))
}
return storeState
}
/**
* 重新封装 mapGetters,使用方法与返回结果跟官方原生 mapGetters 一致,适用全局和 modules 局部
* 推荐使用数组形式获取,写法更简洁
*/
const MAPGETTERS = (...args) => {
const $store = useStore()
const methodName = 'MAPGETTERS'
const target = 'getters'
let modulesName = null
let params = null
const storeGetters = {}
// 检测参数在 getters 中是否存在
const paramsCheckFn = (paramsValusArgs, isModulesName) => {
// 检测参数在 getters 中是否存在
paramsValusArgs.forEach(item => {
if (isModulesName) {
item = `${modulesName}/${item}`
}
if (!Object.keys($store.getters).includes(item)) {
throw new Error(`${methodName}方法里的参数"${item}" Vuex ${target} 中不存在`)
}
})
}
// 调用 mapGetters 的参数为对象
const useObjectTypeFn = (params, modulesName) => {
if (Object.prototype.toString.call(params) === '[object Object]') {
const paramsValusArgs = Object.values(params)
paramsCheckFn(paramsValusArgs, modulesName)
}
}
// 调用 mapGetters 的参数为数组
const useArrayTypeFn = (params, modulesName) => {
if (Array.isArray(params)) {
if (!params.length) throw new Error('${methodName}方法的数组参数长度不能为0')
paramsCheckFn(params, modulesName)
}
}
// computed 优化,给 mapGetters 返回的每个函数的 this 绑定 {$store} 对象
const improveComputedFn = storeGettersFnsObj => {
Object.keys(storeGettersFnsObj).forEach(key => {
const fn = storeGettersFnsObj[key].bind({$store})
storeGetters[key] = computed(fn)
})
}
if (args && args.length === 1) {
// 用户调用全局 Vuex
params = args[0]
useObjectTypeFn(params)
useArrayTypeFn(params)
improveComputedFn(mapGetters(params))
} else if (args && args.length === 2) {
// 用户调用局部模块化 Vuex
modulesName = args[0]
params = args[1]
useObjectTypeFn(params, modulesName)
useArrayTypeFn(params, modulesName)
improveComputedFn(mapGetters(modulesName, params))
}
return storeGetters
}
export { MAPSTATE, MAPGETTERS }
封装完成,现在来个例子,往下看。
新建一个模块文件 store/modules/hobby.js
export default {
namespaced: true, // 模块化时必须要将这个属性设置为 true
state = {
hobby: '不抽烟',
plan: '不喝酒',
like: '不烫头'
},
getters: {
totalHobby: state => `2023 流行 ${state.hobby}-${state.plan}-${state.like}`
}
}
在最新的 store/index.js 文件中引入上面的 modules 模块文件 hobby.js。
import { createStore } from 'vuex'
import hobby from './modules/hobby' // 导入文件
const state = {
msg: 'hello world'
}
const getters = {
newMsg: state => `Hi~ ${state.msg}`
}
const modules = {
hobby // 在模块中使用
}
export default createStore({
state, getters, modules
})
Vue 组件使用
<script setup>
// 导入封装好的方法
import { MAPSTATE, MAPGETTERS } from '@/store/mapStoreReset'
// 获取 state 数据
// 使用方式1 :对象形式
const { msg } = MAPSTATE({msg: 'msg'})
// 使用方式2 :数组形式
const { msg } = MAPSTATE(['msg'])
// 以上两种方式获取的是全局的 state 对象里的数据,结果为
// msg = 'hello world'
// 使用方式3 :对象形式-获取 modules 模块里的 hobby.js 的 state 数据
const { hobby, plan, like } = MAPSTATE('hobby', {hobby: 'hobby', plan: 'plan', like: 'like'})
// 使用方式4 :数组形式-获取 modules 模块里的 hobby.js 的 state 数据
const { hobby, plan, like } = MAPSTATE('hobby', ['hobby', 'plan', 'like'])
// 以上两种方式获取的是 hobby.js 模块(局部)的 state 对象里的数据,结果为
// bobby = '不抽烟' , plan = '不喝酒' , like = '不烫头'
// 获取 getters 数据
// 获取方式1:对象形式
const { newMsg } = MAPGETTERS({newMsg: 'newMsg'})
// 获取方式2:数组形式
const { newMsg } = MAPGETTERS({['newMsg'])
// 以上两种方式获取的是全局的 getters 对象里的数据,结果为
// newMsg = 'Hi~ hello world'
// 获取方式3:对象形式-获取 hobby.js 模块里的 getters 数据
const { totalHobby } = MAPGETTERS('hobby', {totalHobby: 'totalHobby'})
// 获取方式4:数组形式-获取 hobby.js 模块里的 getters 数据
const { totalHobby } = MAPGETTERS({'hobby', ['totalHobby'])
// 以上两种方式获取的是全局的 getters 对象里的数据,结果为
// 20223 流行 不抽烟-不喝酒-不烫头
</script>
本文属个人观点,纯手打,如有错误,欢迎指正,谢谢,完!
更多推荐
所有评论(0)