Vue3 直接使用Vuex的mapState和mapGetters时报错的分析及解决方案

Vuex 提供了 mapStatemapGettersmapActionsmapMutations 四个函数,其返回结果分别是 mappedStatemappedGettermappedAction 和 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>

本文属个人观点,纯手打,如有错误,欢迎指正,谢谢,完!

  • 7
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
mapStatemapGetters是Vue.js中用于将stategetters映射到组件的计算属性的辅助函数。 mapState函数可以将store中的state映射到组件的计算属性中。它接收一个数组或对象作为参数,数组中的元素可以是state中的属性名,或者是一个包含属性名和对应getter的对象。使用mapState后,在组件中就可以直接访问这些计算属性,而不需要在模板中使用this.$store.state来访问。 例如,如果有一个名为count的state属性,可以使用以下方式将其映射到组件的计算属性: ``` import { mapState } from 'vuex' export default { computed: { ...mapState(['count']) } } ``` 这样,在模板中就可以直接使用{{ count }}来访问这个计算属性。 mapGetters函数用于将store中的getters映射到组件的计算属性中。它接收一个数组或对象作为参数,数组中的元素可以是getter函数名,或者是一个包含getter函数名和对应的计算属性名的对象。类似地,使用mapGetters后,在组件中就可以直接访问这些计算属性。 例如,如果有一个名为doubleCount的getter函数,可以使用以下方式将其映射到组件的计算属性: ``` import { mapGetters } from 'vuex' export default { computed: { ...mapGetters(['doubleCount']) } } ``` 这样,在模板中就可以直接使用{{ doubleCount }}来访问这个计算属性。 通过使用这两个辅助函数,可以简化组件中访问stategetters的代码,使代码更加清晰和易读。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值