vue3-vuex4在setup(组合式API)中优雅使用mapState与mapGetters以及module模块【保姆级教程】

2个月前 (03-27) 646次浏览 已收录 1个评论

导读

在vue3中不能像以前vue2那样轻松的使用vuex里的state和getters,因为vue3里的setup没有this,虽然官方给的方法是用计算属性,但是这样就会造成本身需要的计算属性和vuex的计算属性混在一起,所以我们就可以封装一个我们自己的辅助函数。

封装自己的辅助函数

官方示例

首先我们先看官方的用例。

当然也可以展开列表格式的。

...mapState(['count''name']),

在setup里,官方依然是使用计算属性,但是当我们在一个页面中需要很多state数据时,代码就会变得很臃肿。

封装前的分析

既然官方都是放在计算属性里,那我们不妨先从计算属性开始分析。

computed:{
    // funName:function(){return 'value'}
    ...mapState(['count','name']),
  },

从上边的代码可以看出,计算属性里面都是一个一个函数。(关于计算属性可以参考这篇文章计算属性),那么我们就可以得出count也是一个函数。

mapState返回的是一个对象,对象里的一个个key对应一个个函数

computed:{
    // funName:function(){return 'value'}
    ...mapState(['count','name']),
    // 说明count就一个这样的函数
    // count:function(){}
  },

根据上边的分析后,再结合vue2,假如我们理想中的便捷写法如下。

<template>
  <h2>{{count}}</h2>
</template>

<script>
import {mapState,useStore} from 'vuex'
export default {
  name: 'App',
  setup(props) {
    const store = useStore()
    const storeState = mapState(['count','name'])
    return{
      ...storeState
    }
  }
}
</script>

但是结果却不是我们想要的结果。

可以看出来这是一个函数。

接下来我们再根据官方的这句代码以及结合上边的解释

const sCount = computed(()=>{store.state.count}) //computed ref对象

然后我们尝试往计算属性里批量存入这些函数。

setup(props) {
    const storeStateFns = mapState(['count','name'])
    const storeState = {}
    Object.keys(storeStateFns).forEach(fnnKey=>{
      const fn = storeStateFns[fnnKey]
      // {count:ref,name:ref}
      storeState[fnnKey]=computed(fn)
      //这里读fn的时候是根据this.$store.state.count来读的,所以会报错,而且在setup里没有this。
    })
    return{
      ...storeState
    }
  }

但是最后还是爆了错。

报错的愿意就是读fn的时候是根据this.$store.state.count来读的,所以会报错,而且在setup里没有this。

所以我们可以使用bind(可以参考bind

setup(props) {
    const store = useStore()
    const storeStateFns = mapState(['count','name'])
    const storeState = {}
    Object.keys(storeStateFns).forEach(fnnKey=>{
      const fn = storeStateFns[fnnKey].bind({$store:store})
      storeState[fnnKey]=computed(fn)
    })
    return{ 
      ...storeState
    }
  }

此时就可以正常使用了

封装辅助函数

import {mapState,useStore} from 'vuex'
import {computed} from 'vue'
export function useState(mapper){
    //获取到对应的对象的function:{name:function,count:function}
    const storeStateFns = mapState(mapper)

    //把一个个数据转换成ref对象
    const store = useStore()
    const storeState = {}
    Object.keys(storeStateFns).forEach(funKey=>{
      const fn = storeStateFns[funKey].bind({$store:store})
      storeState[funKey]=computed(fn)
    })

    return storeState
}

现在我们就可以像开头期待那样使用了。

<template>
  <h2>{{count}}</h2>
</template>

<script>
import {useState} from './hooks/useState.js'
export default {
  name: 'App',
  setup(props) {
    //可以传数组、对象
    const storeState = useState(['count','name'])
    return{ 
      ...storeState
    }
  }
} 
</script>

对getters封装辅助函数

同样可以使用上边的代码思路当作getters的辅助函数

import {mapGetters,useStore} from 'vuex'
import {computed} from 'vue'
export function useGetters(mapper){
    //获取到对应的对象的function:{name:function,count:function}
    const storeStateFns = mapGetters(mapper)

    //把一个个数据转换成ref对象
    const store = useStore()
    const storeState = {}
    Object.keys(storeStateFns).forEach(funKey=>{
      const fn = storeStateFns[funKey].bind({$store:store})
      storeState[funKey]=computed(fn)
    })

    return storeState
}

使用的话还是和上边一样方便。

<template>
  <!-- <h2>{{count}}</h2>
  <h2>名字 :{{name}}</h2> -->
  <h2>称呼:{{newName}}</h2>
</template>

<script>
import {useState} from './hooks/useState.js'
import {useGetters} from './hooks/useGetters.js'
export default {
  name: 'App',
  setup(props) {
    // const storeState = useState(['count','name'])
    const storeGetters = useGetters(['newName'])
    return{ 
      // ...storeState,
      ...storeGetters
    }  
  }
} 
</script>

至此辅助函数就封装完了,但是我们可能还会发现,这两个辅助函数中的代码还是重复了,所以如果有需要我们还可以把它给抽出来再封装一次。

import {useStore} from 'vuex'
import {computed} from 'vue'
export function useMapper(mapper,mapFn){
    //mapFn对应想使用的函数
    const storeStateFns = mapFn(mapper)

    const store = useStore()
    const storeState = {}
    Object.keys(storeStateFns).forEach(fnnKey=>{
      const fn = storeStateFns[fnnKey].bind({$store:store})
      storeState[fnnKey]=computed(fn)
    })

    return storeState
}

使用的话再传一个想使用的函数即可

useGetters使用辅助函数

import {mapGetters} from 'vuex'
import { useMapper } from './useMapper.js'
export function useGetters(mapper){
    return useMapper(mapper,mapGetters)
}

useState使用辅助函数

import {mapState} from 'vuex'
import { useMapper } from './useMapper.js'
export function useState(mapper){
    return useMapper(mapper,mapState)
}


vuex4的其他补充

mutation辅助函数

因为它返回也是函数,且我们点击后正好是执行函数,所以就不需要像state和getters那样麻烦了,直接食用即可。

<template>
  <h2>{{count}}</h2>
  <!-- 因为这里既然是函数了,所以点击时直接执行了函数 -->
  <button @click="INCREMENT(10)">count增加</button>
</template>

<script>
import {INCREMENT} from './store/mutation_type.js'
import {useState} from './hooks/useState.js'
import {mapMutations} from 'vuex'
export default {
  name: 'App',
  setup(props) {
    const storeState = useState(['count'])
    const storeMutations = mapMutations([INCREMENT])
    return{ 
      ...storeState,
      ...storeMutations
    }  
  }
} 
</script>


module模块

当一个应用变得非常复杂时,store对象就会变得非常臃肿,解决此问题可以将store分割成模块(module)。 每个模块拥有自己的state、mutation、action、getter,甚至还可以继续嵌套子模块。

在没有使用独立命名空间的情况下,它们最后会合并总的store里面,即还是按正常操作来使用。

user.js

const userModule ={
    namespaced:true,//拥有独立的命名空间
    state(){
        return{
            userName:'wxl'
        }
    },
    getters:{
        getUsername(state,getters,rootState,rootGetters){
            return state.userName+'先生'
        }
    },
    mutations:{
        setUserName(state){
            state.userName='zzl'
        }
    },
    actions:{

    }
}

export default userModule

index.js

import {createStore} from "vuex"
import homeModule from './modules/home.js'
import userModule from "./modules/user.js"

const store = createStore({
    state(){
        return{
            rootName:'root'
        }
    },
    mutations:{
        setRootName(state){
            state.rootName='node'
        }
    },

    //模块
    modules:{
        home:homeModule,
        user:userModule
    }
})

export default store

ModuleTest.vue

<template>
<!--模块下,有无命名空间,state都要这样使用-->
  <div>{{ $store.state.user.userName }}</div>
  <div>{{ $store.state.rootName }}</div>

  <!-- 因为使用了独立的命名空间 -->
  <div>{{ $store.getters['user/getUsername'] }}</div>
</template>

<script>
export default {
    methods: {
        setUserClick(){
           //使用了独立的命名空间
            this.$store.commit("/user/setUserName")
        }
    },
}
</script>

<style></style>

默认情况下,模块内部的action、mutation以及getters还是注册在全局的命名空间中的,这样不利于更好的封装和复用,所以可以设置模块的独立命名空间。

这样当模块被注册后,他的所有state、getter、action及mutation都会自动根据模块注册的路径调整命名

独立命名空间和模块下使用辅助函数

和上边非模块下使用辅助函数一样的思路,但是要做少些修改。其中还是只有state与getters需要这样写辅助函数。

userState

import {mapState,createNamespacedHelpers} from 'vuex'
import { useMapper } from './useMapper.js'

// useState['user',['userName','......']]
export function useState(moduleName,mapper){
    let mapperFn = mapState //默认等于mapState
    if(typeof moduleName === 'string' && moduleName.length>0){
         mapperFn=createNamespacedHelpers(moduleName).mapState
    }else{
        //useState('user',['userName','count'])
        //useState(['userName','count'])
        //没有传moduleNam时,但是真正传过来的数组却是给了第一个参数moduleName。
        mapper=moduleName
    }
    return useMapper(mapper,mapperFn)
}

useGetters

import {mapGetters,createNamespacedHelpers} from 'vuex'
import { useMapper } from './useMapper.js'
export function useGetters(moduleName,mapper){
    let mapperFn = mapGetters //默认等于mapGetters
    if(typeof moduleName === 'string' && moduleName.length>0){
         mapperFn=createNamespacedHelpers(moduleName).mapGetters
    }else{
        mapper=moduleName
    }
    return useMapper(mapper,mapperFn)
}

useMapper

import {useStore} from 'vuex'
import {computed} from 'vue'
export function useMapper(mapper,mapFn){
    //mapFn对应想使用的函数
    const storeStateFns = mapFn(mapper)

    const store = useStore()
    const storeState = {}
    Object.keys(storeStateFns).forEach(fnnKey=>{
      const fn = storeStateFns[fnnKey].bind({$store:store})
      storeState[fnnKey]=computed(fn)
    })

    return storeState
}

使用

<template>
  <div>{{ $store.state.user.userName }}</div>
  <button @click="setUserName">修改名字</button>
  <div>{{ $store.state.rootName }}</div>

  <!-- 因为使用了独立的命名空间 -->
  <div>{{ $store.getters['user/getUsername'] }}</div>
 
</template>
 
<script>
//写法1
// import {mapMutations} from 'vuex'
//写法2
import {createNamespacedHelpers} from 'vuex'

import {useState} from './hooks/useState.js'
import {useGetters} from './hooks/useGetters.js'

const {mapMutations} = createNamespacedHelpers('user')
export default {
  setup(props) {
    const state = useState('user',['userName'])
    const getters = useGetters('user',['getUsername'])

    const mutations = mapMutations(['setUserName'])
    return{
      ...state,
      ...getters,
      ...mutations
    }
  }
}
</script>

渣渣龙, 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:vue3-vuex4在setup(组合式API)中优雅使用mapState与mapGetters以及module模块【保姆级教程】
喜欢 (3)

您必须 登录 才能发表评论!

(1)个小伙伴在吐槽
  1. 执行根的Action: dispatch('getParentAction',null,{root:true})
    王学龙2022-04-06 17:52