2018年12月7日 星期五

[Vue] Vuex – advanced


 Vue.js   Vuex 


Introduction


Vuex is the official state management package for Vue.js.
We will learn the advanced usage of Mutations and how to dispatch Actions.




Environment


Vue.js 2.5.11
Vue CLI 3.2.1
Vuex 3.0.01


Mutations


Commit Mutations with Payload

We can commit Mutations with parameter, which is called Payload.
For example, we increase the “count” by a value from Payload in the following Vuex instance.
(PS. Payload is Object in most case.)

import Vue from 'vue';
import Vuex from 'vuex'
Vue.use(Vuex)

export const store = new Vuex.Store({
    state: {
      count: 0
    },
    mutations: {
      increment (state, payload) {
        if(payload) state.count += payload;
        else state.count++;
      },
      //...skip
    }
})

To commit Mutations:
let amt = 10;
store.commit("increment", amt);



Use mapMutations to commit mutations

By injecting the Vuex Store to components, we can map the component methods to store.commit calls by mapMutations helper.

Steps

1.  Make sure injecting Store to components

new Vue({
  el: '#app',
  store,
  //…
})



2.  Define mapMutations by object rest/spread operator

import { mapMutations } from "vuex";

var app = new Vue({
  el: '#app',
  methods: {
    ...mapMutations({
      add: "increment", // Map `this.add()` to `this.$store.commit('increment')`
      minus: "decrement", // Map `this.minus()` to `this.$store.commit('decrement')`
      clear: "reset" // Map `this.reset()` to `this.$store.commit('reset')`
    })
  },
})


Usage

this.add(10); //10 is the optional payload
this.minus();
this.clear();



Of course you can use the original name for the components methods:

methods: {
    ...mapMutations({
      "increment", // Map `this.increment()` to `this.$store.commit('increment')`
      "decrement", // Map `this.decrement()` to `this.$store.commit('decrement')`
      "reset" // Map `this.reset()` to `this.$store.commit('reset')`
    })
}



Actions


Define Actions in Vuex Store

import Vue from 'vue';
import Vuex from 'vuex'

Vue.use(Vuex)

export const INCREMENT = 'increment';
export const DECREMENT = 'decrement';
export const RESET = 'reset';

export const store = new Vuex.Store({
    state: {
      count: 0
    },
    mutations: {
      increment (state, payload) {
        if(payload) state.count += payload;
        else state.count++;
      },
      decrement(state){
          if(state.count > 0)
            state.count--;
      },
      reset(state){
          state.count= 0;
      }
    },
    actions: {
      increment (context, payload) {
        context.commit('increment', payload);
      },
      decrement(context){
        context.commit('decrement');
      },
      reset(context){
        context.commit('reset');
      }
    }
})


Usage: Dispatch Action

//import singleton store
store.dispatch('increment',10); //10 is the payload
store.dispatch('decrement');

//Injected store
this.$store.dispatch('increment',10); //10 is the payload
this.$store.dispatch('decrement');


If the Payload is Object, for example,
myPayload={amt:10}

Here are three ways to dispatch the action,

//1.
store.dispatch('increment', myPayload)
//2.
store.dispatch('increment', {amt:10})
//3.
store.dispatch({
    type: "increment",
    amt: 10
});



Asynchronous Actions

Remeber that Mutations must be synchronous, but Actions can be asynchronous?
Here is how to create asynchronous Actions,

By promise

actions: {
      increment (context, payload) {

        return new Promise((resolve, reject) => {
          setTimeout(() => {
            context.commit('increment', payload);
            resolve();
          }, 3000)
        })
      },
      //...
}


By async/await

actions: {
  async increment (context) {
    context.commit('increment', await getPayload());
  }
  //...
}

The getPayload function is defined as following,
//Assume that getPayload() return promise as following
const getPayload = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(10);
    }, 3000)
  })
}


Usage: Dispatch asynchronous Actions

store.dispatch("increment", amt).then(function(){
  alert("Async increment is done!");
});


Use mapActions to dispatch actions

We can also use mapActions helper to map component methods to Actions.
The Vuex Store injection to components is required.

import { mapActions } from "vuex";

var app = new Vue({
  el: '#app',
  methods: {
    ...mapActions({
      add: "increment", // Map `this.add()` to `this.$store.dispatch('increment')`
      minus: "decrement", // Map `this.add()` to `this.$store.dispatch('decrement')`
      clear: "reset" // Map `this.add()` to `this.$store.dispatch('reset')`
    })
  },
})

Usage: Dispatch Actions thru mapActions

this.add(10) //10 is Payload
this.minus()


Getters


We can use computed property to get or filter the State in Vuex Store, but the computed property can only be used in the component. Vuex allows us to create Getters to reuse the logics in other components.

Define Getters in Vuex Store

class ShopCart {
    constructor() {
        this.items = [];
        this.totalCnt = 0; //Total count for all items in shopcart
        this.totalPrice = 0; //Total pricing for all items in shopcart
    }
}

const store = new Vuex.Store({
  state: new ShopCart(),
  mutations: {
    //...skip
  },
  getters: {
    totalCnt(state) {
      return state.totalCnt;
    },
    totalPrice(state) {
      return state.totalPrice;
    },
    item: (state) => (id) => {
      return state.items.find(x => x.id === id);
    }
  }
})

Notice that if we want to pass the parameter(s) to Getters, define the Getter like this,
(id is the parameter)

item: (state) => (id) => {
      return state.items.find(x => x.id === id);
    }

Which equals to

item(state) {
      return (id) => {
        return state.items.find(x => x.id === id);
      }
    }

Usage of Getters

let total = {
  cnt: store.getters.totalCnt,
  price: store.getters.totalPrice
};

let item = store.getters.item(this.targetId);


mapGetters

Furthermore, we can map component methods to Vuex Getters by mapGetters helper.
PS. Don’t forget injecting Vuex Store to components

computed: {
    // Other computed props   
    ...mapGetters({
      totalCnt: "totalCnt",
      totalPrice: "totalPrice",
      targetItem: "item"
    })
  }


So that we can use the Getters like computed properties,

console.info(this.totalCnt);
console.info(this.totalPrice);
let item = this.targetItem(this.targetId));


You can use the default names of Getters for sure as below,

computed: {
    // Other computed props   
    ...mapGetters([
      "totalCnt","totalPrice","item"
    ])
  }




Watch changes of State

import { store, PUSH, PULL, CLEAR } from "../vuex/shopcart.store.js";+
import { mapGetters } from "vuex";

computed: {
  ...mapGetters({
    currentUser: 'user',
  }),
}
mounted() {
 // Watch the user store
    store.watch( ()=> this.totalPrice, (newVal, oldVal) => {

      // Do something
      console.info(`${oldVal}->${newVal}`);
 });
}





Reference






沒有留言:

張貼留言