导读
在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>