Vuex小知识

107 阅读2分钟

Vue3

一、state

setup中使用mapState

image.png vue2中

    computed() {
         ...mapState(['xx']) // 一个个函数
    }

方法一

通过useStore拿到store后去获取某个状态即可

    const sCounter = computed(() => store.state.counter)

方法二

使用 mapState 的功能

   setup() {
    const store = useStore()
    const sCounter = computed(() => store.state.counter)
    const storeStateFn = mapState(['counter', 'name', 'age', 'height'])
    const storeState = {}
    Object.keys(storeStateFn).forEach(fnKey => {
      /**
       * 报错原因:
       * fnKey 沒有this
       * mapState的本质还是通过this.$store.state.xxx获取
       */
      const fn = storeState[fnKey]
      storeState[fnKey] = computed(fn)
    })
    return {
      sCounter,
      ...storeState
    }
  }

image.png 解决

bind({$store: store})

 setup() {
    const store = useStore()
    const sCounter = computed(() => store.state.counter)
    const storeStateFn = mapState(['counter', 'name', 'age', 'height'])
    const storeState = {}
    Object.keys(storeStateFn).forEach(fnKey => {
      /**
       * fnKey 沒有this
       * mapState的本质还是通过this.$store.state.xxx获取
       * 解决:bind({$store: store}) 指向
       */
      const fn = storeStateFn[fnKey].bind({$store: store})
      storeState[fnKey] = computed(fn)
    })
    return {
      sCounter,
      ...storeState
    }
  }

封装

Vuex并没有提供非常方便的使用mapState的方式,这里进行了一个函数的封装

image.png

import { computed } from "vue"
import { mapState, useStore } from "vuex"

export function useState(mapper) {
    // 拿到store独享
    const store = useStore()
    // 获取到对应的对象的functions: {name: function, age: function}
    // mapState: 支持数组和对象
    const storeStateFn = mapState(mapper)
    // 对数据进行转换
    const storeState = {}
    Object.keys(storeStateFn).forEach(fnKey => {
        console.log(fnKey)
        const fn = storeStateFn[fnKey].bind({$store: store})
        storeState[fnKey] = computed(fn)
    })
    return storeState
}
<template>
  <div>
    <div>home</div>
    <hr>
    <div>{{sCouter}}</div>
    <hr>
    <div>{{sName}}</div>
    <hr>
    <div>{{age}}</div>
    <hr>
    <div>{{height}}</div>
    <hr>
    <br />
  </div>
</template>
<script>
import { useState } from "@/hooks/useState";
export default {
  name: "",
  setup() {
    const storeState = useState(["age", "height"]);
    // 传对象可对其进行重命名,避免命名冲突
    const storeState2 = useState({
      sCouter: state => state.counter,
      sName: state => state.name
    })
    return {
      ...storeState,
      ...storeState2
    };
  },
};
</script>

image.png

二、getters

import { createStore } from 'vuex'
const store = createStore({
    state() {
        return {
            counter: 0,
            name: '小明',
            age: 18,
            height: 188,
            books: [
                { name: 'vue.js', price: 88, count: 1 },
                { name: 'ts', price: 78, count: 2 },
                { name: 'javaScript', price: 68, count: 3 }
            ],
            discount: 0.8
        }
    },
    getters: {
        totalPrice(state) {
            let totalPrice = 0
            for (const book of state.books) {
                // console.log('book', book)
                totalPrice += book.count * book.price
            }
            return totalPrice
        },
        currentDiscount(state) {
            return state.discount * 0.9
        },
        /**
            getters本身不能接收参数
        */
        totalPriceCountGreaterN(state, getters) {
            return function (n) {
                /**
                    n为接收的参数
                */
                let totalPrice = 0
                for (const book of state.books) {
                    // console.log('book', book)
                    if (book.count > n) {
                        totalPrice += book.count * book.price * getters.currentDiscount
                    } else {
                        totalPrice += book.count * book.price
                    }
                }
                return totalPrice
            }
        }
    }
})

export default store
    <div>{{$store.getters.totalPrice}}</div>
    <div>{{$store.getters.totalPriceCountGreaterN(1)}}</div>

使用mapGetters

optionsApi中使用

  computed: {
    ...mapGetters({
      sName: 'nameInfo',
      sAgeInfo: 'ageInfo'
    })
  },
  computed: {
    ...mapGetters(["nameInfo", "ageInfo"]),
  },

在CompositionAPI中使用

  • 方法一
setup() {
    const store = useStore();
    const totalPrice = computed(() => store.getters.totalPrice)
    return {
      totalPrice
    };
  },
  • 方法二
import { computed } from "vue";
import { useStore, mapGetters } from "vuex";
export function useGetter(mappers) {
    const store = useStore()
    const storeGetterFn = mapGetters(mappers)
    const storeGetter = {}
    console.log('storeGetterFn', storeGetterFn)
    Object.keys(storeGetterFn).forEach(FnKey => {
        const fn = storeGetterFn[FnKey].bind({$store: store})
        storeGetter[FnKey] = computed(fn)
    })
    return storeGetter
}
    <template>
  <div>
    <div>home</div>
    <hr />
    <div>{{ $store.getters.totalPrice }}</div>
    <hr />
    <div>{{ totalPrice }}</div>
    <hr />
    <div>{{ totalPriceCountGreaterN(1) }}</div>
    <hr />
    <br />
    <router-view />
  </div>
</template>

<script>
import { useGetter } from '@/hooks/useGetters'
export default {
  name: "",
  setup() {
    const storeGetter = useGetter(["totalPrice", "totalPriceCountGreaterN"])
    return {
      ...storeGetter
    };
  },
};
</script>

state与getters的封装基本一致,只是mapState与mapGetters不同,所以将公共部分进行封装

image.png

import { computed } from "vue"
import { useStore } from "vuex"

export function useMapper(mapper, mapFn) {
    // 拿到store独享
    const store = useStore()
    // 获取到对应的对象的functions: {name: function, age: function}
    // mapState: 支持数组和对象
    const storeStateFn = mapFn(mapper)
    // 对数据进行转换
    const storeState = {}
    Object.keys(storeStateFn).forEach(fnKey => {
        console.log(fnKey)
        const fn = storeStateFn[fnKey].bind({$store: store})
        storeState[fnKey] = computed(fn)
    })
    return storeState
}

三、mutations

image.png

mapMutations辅助函数

  • optionsAPI
    <button @click="increment_n({ n: 1 })">+1</button>
    <br />
    <button @click="incrementN">+1</button>
    methods: {
    ...mapMutations([INCREMENT_N]),
    incrementN() {
      this[INCREMENT_N]({n: 1})
    }
  }
  • CompositionAPI
  // import { INCREMENT_N } from "@/store/mutation-types";
  setup() {
    const mutations1 = mapMutations({
        addNum: 'increment'
    })
    const mutations2 = mapMutations([INCREMENT_N])
    return {
      ...mutations1,
      ...mutations2
    };
  },

mutation重要原则

mutation必須是同步函數

  • devtool工具会记录mutation的日记
  • 每一条mutation被记录,devtools都需要捕捉前一状态和后一状态的快照
  • 但是在mutation中执行异步操作,就无法追踪到数据的变化
  • 所以Vuex的重要原则中要求mutation必须是同步函数

四、actions

使用

// store
    mutations: {
        [INCREMENT_N](state, payload) {
            state.counter += payload.n
        },
        increment(state) {
            state.counter += 1
        }
    },
    actions: {
        incrementA(context, payload) {
            setTimeout(() => {
                context.commit('increment')
                // context.commit(INCREMENT_N, {n: 10})
            }, 1000)
        },
        decrementAction({commit, dispatch, state, rootState, getters, rootGetters}) {
            commit('decrement')
        }
    }

派发

    // 方法一
    incrementN() {
      this.$store.dispatch('incrementA', {name: 'xxx'})
    }
    // 方法二
    incrementN() {
      this.$store.dispatch({
          type: 'incrementA'
      })
    }

辅助函数

// import {  mapActions } from "vuex";
    ...mapActions(['incrementA']),
    ...mapActions({
      add: 'incrementA'
    })
    setup() {
    const actions = mapActions(['incrementA'])
    const actions2 = mapActions({
      add: 'incrementA'
    })
    return {
      ...actions,
      ...actions2
    };
  },

监听结束

    Fn(context) {
        return new Promise((resolve, reject) => {
            axios.get('xxx').then(res => {
                context.commit('xxx', res)
                resolve()
            }).catch(err => {
                rejetc(err)
            })
        })
    }
    onMounted(() => {
        const promise = store.dispatch('Fn')
        promise.then(res => {
            console.log(res)
        }).catch(err => {
            console.log(err)
        })
    })

modules

使用

// index.js
import { createStore } from 'vuex'
import homeModule from './modules/home'
import userModule from './modules/user'
const store = createStore({
   state() {
    return {
        rootCounter: 0
    }
   },
   modules: {
    home: homeModule,
    userModule // key-value一致
   }
})

export default store
// modules/home
const homeModule = {
    state() {
        return {
            homeCounter: 100
        }
    },
    getters: {},
    mutations: {},
    actions: {}
}
export default homeModule
// modules/user
const userModule = {
    state() {
        return {
            userCounter: 100
        }
    },
    getters: {},
    mutations: {},
    actions: {}
}
export default userModule
<template>
  <div>
    <h2>{{$store.state.rootCounter}}</h2>
    <h2>{{$store.state.home.homeCounter}}</h2>
    <h2>{{$store.state.userModule.userCounter}}</h2>
    // getters做了合并
    <h2>{{$store.getters.doubleHomeCounter}}</h2>
  </div>
</template>

namespaced

命名空间

namespaced: true

    const homeModule = {
    namespaced: true,
    state() {
        return {
            homeCounter: 100
        }
    },
    getters: {
        // 模块中,getters的参数有 state, getters, rootState, rootGetters
        // rootState, rootGetters根里面的状态
        doubleHomeCounter(state, getters, rootState, rootGetters) {
            return state.homeCounter * 2
        }
    },
    mutations: {
        increment(state) {
            state.homeCounter++
        }
    },
    actions: {
        increment({commit, dispatch, state, rootState, getters, rootGetters}){
            context.commit('increment')
            // context.commit('increment', payload / null, {root: true}) // root: true表示对根进行提交
            // dispatch('incrementAction', null, {root: true})
        }
    }
}
export default homeModule
    <h2>{{$store.getters["home/doubleHomeCounter"}}</h2>
    methods: {
        homeIncrementAction() {
            this.$store.dispatch("home/incrementAction")
        }
    }

module里面的辅助函数

<template>
  <div>
    <h2>{{ $store.state.rootCounter }}</h2>
    <h2>{{ $store.state.home.homeCounter }}</h2>
    <h2>{{ $store.state.userModule.userCounter }}</h2>
    <hr />
    <h2>{{ homeCounter }}</h2>
    <h2>{{ doubleHomeCounter }}</h2>
    <button @click="increment">+1</button>
  </div>
</template>
<script>
// import { mapState, mapGetters, mapActions, mapMutations } from 'vuex' // 方法1与方法2
import { createNamespacedHelpers } from 'vuex';
// createNamespacedHelpers 帮助工具
const { mapState, mapGetters, mapActions, mapMutations } = createNamespacedHelpers('home')
export default {
  computed: {
    // 方法1
    // ...mapState({
    //   homeCounter: state => state.home.homeCounter
    // }),
    // ...mapGetters({
    //   doubleHomeCounter: 'home/doubleHomeCounter'
    // })
    // 方法2
    // ...mapState('home', ['homeCounter']),
    // ...mapGetters('home', ['doubleHomeCounter']),
    // 方法3 createNamespacedHelpers
    ...mapState(['homeCounter']),
    ...mapGetters(['doubleHomeCounter'])
  },
  methods: {
    // 方法一
    // ...mapMutations({
    //   increment: 'home/increment'
    // }),
    // ...mapActions({
    //   incrementActions: 'home/incrementActions'
    // })
    // 方法2
    // ...mapMutations('home', ['increment']),
    // ...mapActions('home', ['incrementActions'])
    // 方法3 createNamespacedHelpers
    ...mapMutations(['increment']),
    ...mapActions(['incrementActions'])
··  }
}
</script>

在setup中

之前已封装了mapState、mapGetters, 但是 并没有考虑到模块的情况

import { mapState, createNamespacedHelpers } from "vuex"
import { useMapper } from './useMapper'
export function useState(moduleName, mapper) {
    let mapperFn = mapState
    if (typeof moduleName === 'string' && moduleName.length > 0) {
        mapperFn = createNamespacedHelpers(moduleName).mapState
    } else {
        mapper = moduleName
    }
    return useMapper(mapper, mapperFn)
}

createNamespacedHelpers(moduleName) 拿到指定模块的辅助函数

import { mapGetters, createNamespacedHelpers } from "vuex"
import { useMapper } from './useMapper'
export function useState(moduleName, mapper) {
    let mapperFn = mapGetters
    if (typeof moduleName === 'string' && moduleName.length > 0) {
        mapperFn = createNamespacedHelpers(moduleName).mapGetters
    } else {
        // 如果不传模块名称
        mapper = moduleName
    }
    return useMapper(mapper, mapperFn)
}
import { mapMutations, mapActions } from "vuex";
import { useState, useGetters } from "@/hooks";
export default {
  setup() {
    const state = useState("home", ["homeCounter"]);
    const getter = useGetters("home", ["doubleHomeCounter"]);
    const mutations = mapMutations("home", ["increment"]);
    const actions = mapActions("home", ["incrementActions"]);
    return {
      ...state,
      ...getter,
      ...mutations,
      ...actions,
    };
  },
};

学习笔记,codeWhy