Introduction

import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

const store = new Vuex.store(/* ... */)

const app = new Vue({
  store
})

State

Single Source State

// Vuex Setting

import Vuex from 'vuex'

const store = new Vuex.store({
  state: { count: 0 }
})

// Component

const componentOption = {
  computed: {
    count() { return this.$state.count }
  }
}

使用 mapState(...) API 簡化綁定過程

import { mapState } from 'vuex'

const componentOption = {
  computed: {
    ...mapState(['count']),
    ...mapState({
      aliasCount: 'count',
      functCount: state => state.count
    })
  }
}

Getter

形同 vue component option 中的 computed

// Vuex Setting

import Vuex from 'vuex'

const store = new Vuex.store({
  state: { count: 0 },
  getters: {
    text(state, getters) { return `Count: ${state.count}` },
    decoText(state, getters) { return `Deco ${getters.text}` }
  }
})

// Component

const componentOption = {
  computed: {
    text() { return this.$store.state.getters.text }
  }
}

Method-Style Access

使用 function 的方式調用 getter,且可以傳遞參數:

// Vuex Setting

import Vuex from 'vuex'

const store = new Vuex.store({
  state: { count: 0 },
  getters: {
    text(state, getters) {
      return (prefix) => `${prefix} Count: ${state.count}`
    }
  }
})

// Component 中調用

store.getters.text('Hola')

使用 mapGetters 簡化綁定過程

用法同 mapState

import { mapState } from 'vuex'

const componentOption = {
  computed: {
    ...mapGetters(['text'])
  }
}

Mutation

  • 在 Mutation 內部修改 state。
  • mutation 內部的實作必須為 sync 的。
  • mutation 的名稱建議設置為常數以便存取。
// Vuex Setting

import Vuex from 'vuex'

const INCREMENT = 'increment'
const VALUE = 'value'

const store = new Vuex.store({
  state: { count: 0, value: "" },
  mutation: {
    [INCREMENT](state) { state.count++ },
    // 可以傳遞參數 val
    [VALUE](state, payload) { state.value = payload.value }
  }
})

// Component 中調用

this.$store.commit(INCREMENT)
this.$store.commit(VALUE, { value: 'Hola' })
this.$store.commit({ type: VALUE, value: 'Hola' })

使用 mapMutations 綁定

// Component 

import { mapMutations } from 'vuex'

const componentOption = {
  methods: {
    ...mapMutations([add]),
    callAdd() {
      // Mapping 後的呼叫方式
      this.add();
    }
  }
}

const componentOption = {
  data() {
    return { input: String }
  },
  methods: {
    ...mapMutations({
      add: INCREMENT,
      input: VALUE
    })
	}
}

// Component 中調用

this.add()
this.input(input)

Action

  • 內部實作可為 async 的 mutation。
  • 內部實作調用 mutation。
  • 元件中呼叫 store.dispatch 調用 action。
  • 可以在 action 中透過 dispatch ,呼叫其他 action。
  • store.dispatch(/* ... */) 回傳 Promise,可以依此來進行 async 行為設計(例如相依的修改、有優先順序的 ajax)。
// Vuex Setting

const INCREMENT = 'increment'

const store = new Vuex.store({
  state: { count: 0 },
  mutations: {
    [INCREMENT](state) { state.count++ }
  },
  actions: {
    [INCREMENT]({ commit, dispatch, state }, payload) { setTimeout(() => commit('increment'), 1000) }
  }
})

// Component 中調用

this.$store.dispatch(INCREMENT)

使用 mapActions 綁定

用法同 mapState

// Component

import { mapActions } from 'vuex'

const componentOption = {
  methods: {
    ...mapActions([INCREMENT])
  }
}

Modules

讓你可以依據設計將 store 的設計拆分。

const moduleA = {
  state: { /* ... */ },
  mutations: { /* ... */ },
  actions: { /* ... */ },
  getters: { /* ... */ }
}

const moduleB = {
  state: { /* ... */ },
  mutations: { /* ... */ },
  actions: { /* ... */ }
}

const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
  }
})

store.state.a // -> `moduleA`'s state
store.state.b // -> `moduleB`'s state

module 內部實作時各 API 得到的第一個參數皆指向 module 內部的 scope。

const moduleA = {
  state: { count: 0 },
  mutations: {
    // state 為 moduleA scope
    increment(state) {
      state.count++
    }
  },
  getters: {
    /*
     * state, getter 為 moduleA scope
     * rootState, rootGetter 為 root scope
     */
    doubleCount(state, getters, rootState, rootGetter) {
      return state.count * 2
    }
  },
  actions: {
    /**
     * state, getter 為 moduleA 內部
     * rootState, rootGetter 為 root scope
     */
    incrementIfOddOnRootSum ({ state, getter, commit, rootState, rootGetter }) {
      if ((state.count + rootState.count) % 2 === 1) {
        commit('increment')
      }
    }
  }
}

Namespacce

預設情況下,不同 modules 的 getters、mutations、actions 都會被註冊在 global namesapce:

const moduleA = {
  getters: {
    holaA() { return 'Hola in ModuleA' }
  }
}

const moduleB = {
  getters: {
    holaB() { return 'Hola in ModuleB' }
  }
}

const store = new Vuex.store({
  modules: {
    a: moduleA,
    b: moduleB
  }
})

store.getters.holaA;
store.getters.holaB;

如果要將某 module 自 global 中拆開,則在該 module 中設定 namespace: true

const moduleA = {
  namespaced: true,
  getters: {
    holaA() { return 'Hola in ModuleA' }
  }
}

const moduleB = {
  getters: {
    holaB() { return 'Hola in ModuleB' }
  }
}

const store = new Vuex.store({
  modules: {
    a: moduleA,
    b: moduleB
  }
})

store.getters['a/holaA'];
store.getters.holaB;

在 Namespace 中呼叫 root 中的 mutations, actions

在 module 中如果要使用 root 上的 mutations, actions 時, 在 commit(/* ... */) 第三參數傳入 { root: true}

import Vuex from 'vuex'

const moduleA = {
  namesapced: true,
  
  state: { value: 'A' }
  
  mutations: {
    ['UPDATE'](state, payload) { state.value = payload.value }
  },
    
  actions: {
    ['UPDATE_ROOT_VALUE'](context, payload) {
      // 呼叫 root 中的 'UPDATE' commit, 本例中應為 moduleB.mutations['UPDATE']
      context.commit('UPDATE', payload, { root: true })
    }
  }
}

const moduleB = {
  state: { value: 'B' },
  
  mutations: {
    ['UPDATE'](state, payload) { state.value = payload.value }
  }
}

const store = new Vuex.Store({
  modules: {
    moduleA,
    moduleB
  }
})

store.dispatch('moduleA/UPDATE_ROOT_VALUE', { value: 'hello world!!' })
console.log(store.state.moduleB.value) // hello world!!

在 Namespace Module 中註冊全局的 action

import Vuex from 'vuex'

const moduleA = {
  namesapced: true,
  
  state: { value: 'A' }
  
  mutations: {
    ['UPDATE'](state, payload) { state.value = payload.value }
  },
    
  actions: {
    ['UPDATE_FROM_GLOBAL_SCOPE'] {
      root: true,
      handler(context, payload) {
        context.commit('UPDATE', payload)
      }
    }
  }
}

const store = new Vuex.Store({
  modules: { moduleA }
})

store.dispatch('UPDATE_FROM_GLOBAL_SCOPE', { value: 'hello world!!' });
console.log(store.state.moduleA.value) // hello world!!

使用 mapState, mapMutations, mapActions 快速註冊 Namespace Module

interface mapState<S> {
  (
    modulePath: string, // 例如: 'moduleA'
    bindedState: Array<keyof S> | Object<string, (keyof S | (state: S) => any)>
	): Object<string, any>
}

interface mapMutations<S> {
  (
    modulePath: string,
    bindedState: Array<keyof S> | Object<string, (keyof S | (commit, ...args: any[]) => void)>
	): Object<string, Function>
}

interface mapActions<S> {
  (
    modulePath: string,
    bindedState: Array<keyof S> | Object<string, (keyof S | (dispatch, ...args: any[]) => void)>
	): Object<string, Function>
}

Dynamic Register Module

import Vuex from 'vuex'

const store = new Vuex.Store({})

const moduleA = /* 某個 module */
store.registerModule('moduleA', moduleA)

const childModule = /* moduleA 的子 module */
store.registerModule(['moduleA', 'childModule'], childModule)

如果註冊 moduleA 之前已從 server-side render 拿到 moduleA 的 state 值, 可以在 dynamic registration 時予以保留:

store.registerModule('moduleA', moduleA, { preserveState: true });

State Pollution

如果重複註冊同個 module,如果 module 中的 state 宣告為 Object,則會造成 State Pollution,因此建議 state 宣告為 function:

const moduleA = {
  state() { return { value: 'A' } }
}

Hot Reload

import Vuex from 'vuex'

const store = new Vuex.Store({ /* ... */ })

import('./newModules')
	.then(module => {
		store.hotUpdate({ /* ...同 Vuex.Store 的參數 */ })
	});