Blog of CodeJELLOW’S ENGINEERING NOTES
← 返回文章

VUE3之VUEX

VUE3之VUEX vuex的基本使用 state getters mutations actions 辅助函数 modules

vuex的基本使用

创建一个store:

TEXT
import {createStore} from 'vuex'

const store = createStore();

export default store;

在Main.js中使用:

TEXT
import store from './store'

createApp(App).use(store).mount('#app')

state

在store中定义数据,需要在state属性中:

TEXT
const store = createStore({
  state(){
    return{
      counter:0
    }
  }
});

在组件中使用时,需要从$store.state中获取:

TEXT
<h2>{{$store.state.counter}}</h2>

mutations

在vuex的开发模式中,不推荐直接对数据进行操作,而是应该通过commit来提交一次数据操作。在store中定义mutations属性:

TEXT
  mutations:{
    increment(state){
      state.counter++
    },
    decrement(state){
      state.counter--
    }
  }

在组件中调用store的commit方法,参数是要执行的mutation的方法名:

TEXT
  methods:{
    increment(){
      this.$store.commit("increment")
    },
    decrement(){
      this.$store.commit("decrement")
    }
  }

mutations的一些补充

mutations中的方法的第一个参数是state,可以获取state中的变量,第二个参数是payload,即从外部传入的参数:

TEXT
  mutations:{
    incrementN(state,payload){
      state.counter+=payload
    }
  },
TEXT
    methods: {
      addTen(){
        this.$store.commit('incrementN',10)
      }
    },

另一种提交的风格是,将两个参数合并,即传入一个对象,当中有type来指明提交给某个函数:

TEXT
this.$store.commit({
  type:'incrementN',
  incre:10
})

另外为了防止将方法名写错,可以使用常量的方式定义方法名:

TEXT
  const INCREMENT_N = "increment_n";
    mutations:{
    [INCREMENT_N](state,payload){
      state.counter+=payload
    }
  },

mapState辅助函数

可以通过计算属性来简化获取state中的数据:

TEXT
  computed:{
    sCounter(){
      return this.$store.state.counter
    }
  }

不过vuex为我们提供了一个函数,可以更加快速地获取到state中的变量:

TEXT
import { mapState } from 'vuex'
  export default {
    computed:{
      sCounter(){
        return this.$store.state.counter
      },
      ...mapState(["counter","name"])
    }
  }

mapState传入的是要获取的state中的变量名数组,返回值是变量的computed对象,所以需要用…展开;也可以传入一个对象,可以自定义名字:

TEXT
...mapState({
  sCounter:state=>state.counter,
  name:state=>state.name
})

在composition API中使用

vuex提供了一个useStore方法让我们在setup中可以获取到store:

TEXT
import { useStore } from 'vuex'
import { computed } from 'vue'
  export default {
    setup(props) {
      const store = useStore();

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

      return {
        sCounter
      }
    }
  }

如果要使用mapState,需要进行一些处理:

TEXT
setup(props) {
      const store = useStore();

      const storeStateFns = mapState(["counter","name"]);
      const storeState = {};
      Object.keys(storeStateFns).forEach(fnKey =>{
        const fn = storeStateFns[fnKey].bind({$store:store});
        console.log(fnKey);
        storeState[fnKey]=computed(fn);
      })

      return {
        ...storeState 
      }
    }

mapState返回的都是函数,在执行时需要用到$store属性,所以需要加上bind。

对以上的内容进行封装以便使用:

TEXT
      const storeStateFns = mapState(["counter","name"]);
      const storeState = {};
      Object.keys(storeStateFns).forEach(fnKey =>{
        const fn = storeStateFns[fnKey].bind({$store:store});
        console.log(fnKey);
        storeState[fnKey]=computed(fn);
      })

getters

一些数据可能需要结果处理之后再进行显示(和computed类似,不用在每一个组件中写计算属性),定义方式如下:

TEXT
const store = createStore({
  state(){},
  mutations:{},
  getters:{
    totalPrice(state){
      let totalPrice=0;
      for (const book of state.books){
        totalPrice+=book.count*book.price
      }
      return totalPrice;
    }
  }
});

使用方式:

TEXT
<h2>总价值:{{$store.getters.totalPrice}}</h2>

getters中定义的函数的第一个参数是state,可以获取state中定义的变量,第二个参数是getters,可以用其调用getters中其它的函数:

TEXT
  getters:{
    totalPrice(state,getters){
      let totalPrice=0;
      for (const book of state.books){
        totalPrice+=book.count*book.price
      }
      return totalPrice * getters.currentDiscount;
    },
    currentDiscount(state){
      return state.discount*0.9;
    }
  }

getter函数的返回值可以是一个值,在使用时就应该如同上面所示:

TEXT
<h2>总价值:{{$store.getters.totalPrice}}</h2>

也可以返回一个函数,返回函数的一个目的是传入我们的自定义参数:

TEXT
totalPriceForCountN(state,getters){
  return function(n){
    let totalPrice=0;
    for (const book of state.books){
      if (book.count>=n){
        totalPrice+=book.count*book.price
      }
    }
    return totalPrice * getters.currentDiscount;
  }
}

使用时需要在后面加上小括号表示函数调用:

TEXT
<h2>总价值:{{$store.getters.totalPriceForCountN(4)}}</h2>

mapGetters

使用方式和mapState类似。在computed中:

TEXT
import { mapGetters } from 'vuex'
computed:{
  ...mapGetters(["nameInfo","counterInfo"])
},

同样也有数组写法和对象写法。

在setup中使用mapGetters,也如同mapState一样需要进行一些处理:

TEXT
import { computed } from 'vue'
import { mapGetters, useStore } from 'vuex'

export function useGetters(mapper){
  const store = useStore();
  const storeGettersFns = mapGetters(mapper);

  const storeGetters = {}
  Object.keys(storeGettersFns).forEach(fnKey=>{
    const fn = storeGettersFns[fnKey].bind({$store: store});
    storeGetters[fnKey]=computed(fn);
  })

  return storeGetters;
}

mapMutations

在computed中使用:

TEXT
import {mapMutations} from 'vuex'
  export default {
    methods: {
      ...mapMutations(["increment","decrement","increment_n"])
    }
  }

在setup中使用:

TEXT
import {mapMutations} from 'vuex'
  export default {
    setup(props) {
      const storeMutations = mapMutations(["increment","decrement","increment_n"]);
      return {
        ...storeMutations
      }
    }
  }

actions

action中可以进行异步的操作,处理完的结果也需要通过commit方法让mutations进行数据处理。

actions中的方法有一个context参数,它类似于store,但是在modules中可能会有所不同:

TEXT
  actions:{
    incrementAction(context){
      setTimeout(() => {
        context.commit("increment")
      }, 1000);
    }
  }

第二个参数同mutations中的一样,是一个从外面传入的参数:

TEXT
  actions:{
    incrementAction(context,payload){
      console.log(payload)
      setTimeout(() => {
        context.commit("increment")
      }, 1000);
    },
TEXT
    methods: { 
      increment(){
        this.$store.dispatch("incrementAction",{name:"shopkeeper"});
      }
    },

在组件中调用actions的方式:

TEXT
    methods: { 
      increment(){
        this.$store.dispatch("incrementAction");
      }
    },

一般来说,我们会在actions编写网络请求数据的操作,以下是一个结合axios的小例子:

TEXT
  actions:{
    getHomeUserData(context){
      axios.get("http://139.155.47.131/users").then(
        res=>{
          context.commit("addUserData",res.data)
        }
      )
    }
  }
TEXT
    mounted() {
      this.$store.dispatch("getHomeUserData");
    },

context还有其它的一些属性,可以在需要的时候调用:

图片暂不可用。

actions如同mutations一样有另一种派发风格,即写成一个对象:

TEXT
decrement(){
  this.$store.dispatch({
    type:"decrementAction"
  })
}

mapActions

在methods中使用:

TEXT
methods: {
  ...mapActions(["incrementAction","decrementAction"]),
  ...mapActions({
    add:"incrementActions",
    sub:"decrementActions"
  })
},

在setup中使用:

TEXT
setup(props) {
  const actions = mapActions(["incrementAction","decrementAction"])
  return {
    ...actions
  }
}

actions的返回值

actions的返回值默认为undefined,实际上可以返回一个promise,以监听数据何时加载完毕:

TEXT
  actions:{
    getHomeUserData2(context){
      return new Promise((resolve,reject)=>{
        axios.get("http://139.155.47.131/users").then(
          res=>{
            context.commit("addUserData",res.data)
            resolve("success")
          }
        ).catch(err =>{
          reject(err)
        })
      })
    },
  }
TEXT
    setup(props) {
      const store = useStore();
      onMounted(()=>{
        const promise=store.dispatch("getHomeUserData2");
        promise.then(res=>{
          console.log(res)
        }).catch(err=>{
          console.log(err)
        })
      })
    }

modules

基本使用:为每一个组件单独定义一个module对象,其中包含state、mutations等store中的属性。在根store中的modules属性中配置之:

TEXT
const homeModule = {
  state(){
    return {
      counter: 100
    }
  },
  getters:{

  },
  mutations:{

  },
  actions:{

  }
}

export default homeModule
TEXT
import {createStore} from 'vuex';
import home from './modules/home';
import user from './modules/user';
const store = createStore({
  modules:{
    home,
    user 
  }
});

export default store;

modules的命名空间

默认情况下:modules中只有state是独占的(需要使用state.xxx获取),其它的属性mutations等都是合并到根store中。此时使用getters、mutations或者actions,将会触发所有同名的变量。

如果想让module独占其内部的属性,需要加上namespaced:true:

TEXT
const homeModule = {
  namespaced:true,
  state(){
    return {
      counter: 100
    }
  },
  getters:{
    doubleHomeCounter(state){
      return state.counter*2;
    }
  },
  mutations:{
    increment(state){
      state.counter++;
    }
  },
  actions:{
    incrementAction(context){
      context.commit("increment")
    }
  }
}

export default homeModule

需要通过类似于url的方式访问:

TEXT
<template>
  <div>
    <hr>
    <h2>当前home计数:{{$store.state.home.counter}}</h2>
    <button @click="homeIncrement">home+1</button>
    <h2>{{$store.getters["home/doubleHomeCounter"]}}</h2>
    <button @click="homeIncrementAction">home increment action</button>
    <hr>
  </div>
</template>

<script>
import {useStore, mapActions} from 'vuex'
  export default {
    methods: {
      homeIncrement(){
        this.$store.commit("home/increment")
      },
      homeIncrementAction(){
        this.$store.dispatch("home/incrementAction")
      }
    },
  }
</script>

modules的其它一些细节

module中的getters方法,除了本有的state和getters参数外,还能获取到rootState和rootGetters。

TEXT
  getters:{
    doubleHomeCounter(state,getters,rootState,rootGetters){
      return state.counter*2;
    }
  },

module中的actions方法,其入参context对象有commit、dispatch、state、rootState、getters、rootGetters属性。

TEXT
  actions:{
    incrementAction({commit, dispatch, state, getters, rootState, rootGetters}){
      commit("increment")
    }
  }

在commit和dispatch中,如果想要对根中的mutations操作,需要在第三个参数中增加{root: true}:

TEXT
commit("increment",null,{root: true})

modules中的辅助函数

如何对modules使用mapState等辅助函数,在options API中有两种写法,一种是直接通过根store来获取,那么需要一个namespace的参数;另一种是使用createNamespacedHelpers函数来对module进行操作:

TEXT
import { createNamespacedHelpers} from 'vuex'
const {mapState,mapGetters,mapMutations,mapActions} = createNamespacedHelpers("home")
  export default {
    computed:{      
      //写法1:直接从根中获取
      // ...mapState("home",["counter"]),
      // ...mapGetters("home",["doubleHomeCounter"])

      // 写法2:从home中获取
      ...mapState(["counter"]),
      ...mapGetters(["doubleHomeCounter"])
    },
    methods: {
      // 写法1:直接从根中获取
      // ...mapMutations("home",["increment"]),
      // ...mapActions("home",["incrementAction"])

      // 写法2:从home中获取
      ...mapMutations(["increment"]),
      ...mapActions(["incrementAction"])
    },
  }

在composition API中:

通过 RSS 订阅更新 →