jtwang7 / Vue-Note

Vue 学习笔记
0 stars 0 forks source link

Vue3 setup 中 mapState 和 computed 结合的正确“食用”方式 #12

Open jtwang7 opened 2 years ago

jtwang7 commented 2 years ago

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;
}