-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Vue3 setup 中 mapState 和 computed 结合的正确“食用”方式
参考文章:
🔆 问题场景:在 Vue 中访问 Vuex 的状态 state 和计算属性 getters 时,为了保留取出变量的响应性,需要用 computed 包裹为计算属性。在 Vue 的与组件实例绑定的 computed 属性中,官方提供了一系列配套的组建绑定的辅助函数 ,例如 mapState, mapGetters, mapMutations 等帮助开发者更方便的遍历包裹状态变量,保留响应性并添加到 Vue 中。但是在 Vue3 的 setup 中,单独使用 mapState 等辅助函数无效(原因见下),因此需要做二次封装。
❎ 简单的解决方案(不推荐) - 直接用 computed
import {computed} from "vue"
import {mapState, useStore} from "vuex"
export default {
setup() {
const store = useStore()
const counter = computed(() => store.state.counter)
return {
counter
}
}
}
直接用 computed ,会写出很多重复性的代码
const counter = computed(() => store.state.counter)
const name = computed(() => store.state.name)
const age= computed(() => store.state.age)
const counter = computed(() => store.state.counter)
const counter = computed(() => store.state.counter)
✅ mapState + computed = useState
mapState
实际是将 store 内 state 的各项属性值用 computed 包裹,保留其响应性,然后以对象的形式抛出,最终输出结果如下:
/**
* mapState返回的数据结构:
* {
* name: function(){return 'xxx'},
* age: function(){return 'xxx'}
* }
*/
mapState
这样组织数据的原因是因为要匹配 Vue 内与组件实例绑定的 computed 属性字段的映射格式 (接收一个对象,其键值为一个函数),参考官方文档 computed。
知道了 mapState
等辅助函数的作用原理及返回的数据格式,我们就可以自己封装一个函数,该函数的目的就是将 mapState
等辅助函数返回的值重新用 computed()
再次封装。
🔆 值得注意的是:mapState
等一系列辅助函数在解析 state 或者 getters 等数据时,是需要通过 this.$store
去解析的,这个在组件实例中不需要考虑,因为函数会自动引用组件实例的 this,但是在 setup 中,由于没有 this ,所以我们给它的函数单独绑定 ctx。
import {mapState, useStore} from "vuex"
import {computed} from "vue"
export default {
setup() {
const store = useStore()
const storeStateFns = mapState(['name', 'age', 'gender'])
/**
* mapState返回的数据结构:
* {
* name: function(){return 'xxx'},
* age: function(){return 'xxx'}
* }
*/
const storeState = {}
Object.keys(storeStateFns).forEach(fnKey => {
// mapState在解析state的数据时,是需要通过this.$store去解析
// 在setup里面是没有this的,所以我们给它的函数绑定ctx
// this => {$store: store}
const fn = storeStateFns[fnKey].bind({$store: store})
// 遍历生成这种数据结构 => {name: ref(), age: ref()}
storeState[fnKey] = computed(fn)
})
return {
...storeState
}
}
}
✅ 封装为 hook 方便使用
import { computed } from "vue"
import { mapState, useStore } from "vuex"
export default function (state) {
// 1. 获取实例 $store
const store = useStore()
// 2. 遍历状态数据
const storeStateFns = mapState(state)
// 3. 存放处理好的数据对象
const storeState = {}
// 4. 对每个函数进行computed
Object.keys(storeStateFns).forEach(fnKey => {
const fn = storeStateFns[fnKey].bind({ $store: store })
// 遍历生成这种数据结构 => {name: ref(), age: ref()}
storeState[fnKey] = computed(fn)
})
return storeState
}
✅ 实例
import { computed, ComputedRef } from "vue";
import {
mapState,
mapGetters,
mapMutations,
MutationMethod,
} from "vuex";
import _ from "lodash";
import store from "@/store";
export type StoreStateMap = Record<string, ComputedRef<any>>;
export type StoreMutationMap = Record<string, MutationMethod>;
export function useState(map: any): StoreStateMap;
export function useState(namespace: string, map: any): StoreStateMap;
export function useState(namespace?: string, map?: any): StoreStateMap {
let state = null;
if (namespace) {
state = mapState(namespace, map);
} else {
state = mapState(map);
}
// 借助 lodash _.mapValues() 简化操作
const computedState: StoreStateMap = _.mapValues(state, (computedFunc) =>
computed(computedFunc.bind({ $store: store }))
);
return computedState;
}
export function useGetters(map: any): StoreStateMap;
export function useGetters(namespace: string, map: any): StoreStateMap;
export function useGetters(namespace?: string, map?: any): StoreStateMap {
let getters = null;
if (namespace) {
getters = mapGetters(namespace, map);
} else {
getters = mapGetters(map);
}
const computedState: StoreStateMap = _.mapValues(getters, (computedFunc) =>
computed(computedFunc.bind({ $store: store }))
);
return computedState;
}
export function useMutations(map: any): StoreMutationMap;
export function useMutations(namespace: string, map: any): StoreMutationMap;
export function useMutations(namespace?: string, map?: any): StoreMutationMap {
let mutations = null;
if (namespace) {
mutations = mapMutations(namespace, map);
} else {
mutations = mapMutations(map);
}
const wrappedMutations: StoreMutationMap = _.mapValues(mutations, (func) =>
// mutations 等不需要包裹为 computed 响应式,直接绑定 store 即可
func.bind({ $store: store })
);
return wrappedMutations;
}