vuex 深刻學習

概述

在開發 複雜vue應用 時,vuex 能夠幫咱們管理 多個組件中的共享狀態vue

使用 new Vuex.store(options), 能夠構建一個 store 實例。store 是 vuex應用 的核心,經過 store.state 能夠訪問應用中的 共享狀態, 經過 store.getters 能夠訪問 共享狀態派生出的新的狀態,經過 store.commit 方法能夠提交 mutation更改共享狀態,經過 store.dispatch 方法能夠 派發 action異步提交 mutaionvuex

另外, 若是 應用變得複雜致使store變得比較臃腫 的時候, 咱們能夠將 store 分割成 module, 每一個 module 可擁有本身的 stategettersmutationsactions 甚至 嵌套子modules數組

問題

  1. vuex是怎麼安裝的? 安裝過程當中作了哪些工做?緩存

  2. 爲何修改state中的數據,會觸發更新?bash

  3. getter的工做原理?app

  4. 爲何不建議經過 store.state.xx 的方式直接修改vuex的狀態?異步

  5. 命名空間對state、getter、mutation、action的影響ide

  6. 嚴格模式是怎麼工做的?函數

  7. state、getter、mutation、action在組件中的使用ui

  8. 其餘

安裝 vuex

使用 vuex 開發 vue應用 的時候,須要先安裝 vuex

vuex 的安裝過程以下:

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

Vue.use(Vuex)
複製代碼

vue 在經過 use 安裝 vuex 的時候, 會自動執行 vuex 提供的 install方法。 vuex 提供的 install 方法以下:

// install

function install (_Vue) {
  if (Vue && _Vue === Vue) {
    {
      console.error(
        '[vuex] already installed. Vue.use(Vuex) should be called only once.'
      );
    }
    return
  }
  Vue = _Vue;
  // 執行applyMixin方法
  applyMixin(Vue);
}
複製代碼

vuex 提供的 install 方法中,主要過程是執行 applyMixin 方法,具體過程以下:

// applyMixin

function applyMixin (Vue) {
  // 獲取當前使用的vue的版本
  const version = Number(Vue.version.split('.')[0]);

  if (version >= 2) {
    // vue2及以上 使用,每個vue實例構建時觸發 beforeCreate 鉤子函數, 執行 vuexInit 方法
    Vue.mixin({ beforeCreate: vuexInit });
  } else {
    // vue1 使用
    // override init and inject vuex init procedure
    // for 1.x backwards compatibility.
    const _init = Vue.prototype._init;
    Vue.prototype._init = function (options = {}) {
      options.init = options.init
        ? [vuexInit].concat(options.init)
        : vuexInit;
      _init.call(this, options);
    };
  }
  
  // 每個vue實例構建的時候, 都會執行vuexInit方法
  function vuexInit () {
    // this -> 當前vue實例
    const options = this.$options;
    // store injection
    if (options.store) {
      // 根vue實例 的 $store 屬性
      this.$store = typeof options.store === 'function'
        ? options.store()
        : options.store;
    } else if (options.parent && options.parent.$store) {
      // 組件vue實例, $store屬性, 指向根vue實例的$store屬性
      this.$store = options.parent.$store;
    }
  }
複製代碼

applyMixin 方法,全局註冊 beforeCreate 鉤子,即 每個vue實例在構建的時候都會觸發 beforeCreate 鉤子,執行 vuexInit 方法

執行 vuexInit 方法的時候, 爲 vue實例 建立 $store屬性。不論是 根vue實例 仍是 組件vue實例$store屬性 都指向經過 new Vuex.store 構建的 store實例,即 全部的 vue實例 的 $store 屬性值都相同

綜上, vuex 安裝的時候就作了 一件事:給 vue全局註冊 beforeCreate。當建立 vue實例 的時候,觸發 beforeCreate,給 vue實例 添加 $store 屬性,指向經過 new Vuex.store 構建的 store實例

vuex - 響應式工做原理

在使用 vuex 開發 vue應用 的時候, 咱們會定義 state, 而後在 組件 中經過 計算屬性(computed) 使用定義好的 state。 以下:

var option = {
    state: {
        money: 10000
    },
    mutations: {
        setMoney(state, money) {
            state.money = money
        }
    }
}

var bill = {
    data() {
        return {...}
    },
    computed: {
        finalMoney() {
            // money的單位爲 釐
            return parseInt(this.$store.state.money / 1000)
        }
    },
    methods: {
        setDisount() {
            this.$store.commit('setMoney', this.money)
        }
    }
    ...
    
}
複製代碼

在上面的 示例 中,帳單組件 在計算 費用 的時候,使用了 vuex狀態 - money。 當經過 組件方法 setMoney 修改 vuex狀態 - money, 帳單組件以前計算的費用 也會 更新

那麼問題來了, vuex的響應式更新是怎麼觸發的?

其實,vuex的響應式原理 是基於 vue的響應式原理 實現的

構建vue實例 的時候,若是 配置項 中包含 數據屬性data, 會 深度遍歷data中的每個屬性,爲 每個屬性 創建一個 deps列表,並經過 defineProperty 方法爲 每個屬性 添加 gettersetter。若是 template模板計算屬性監聽屬性 使用了 data屬性, 會 觸發data屬性的getter,對應的 watcher 會添加到 data屬性的deps列表 中。 當 data屬性 發生變化時, 觸發setter通知deps列表中的watcher更新 ,而後 從新渲染界面返回新的計算屬性的值觸發監聽callback

vuex響應式更新, 是在使用 new Vuex.store(options) 構建 store實例 的時候 實現 的,經歷的 主要流程 以下:

  1. 創建 根module 以及 子modules

    在構建 store實例 的時候,首先會根據傳入的 配置項 options(state、getters、mutation等) 生成一個 root module對象。若是 options 中包含 modules以及嵌套modules, 那麼會遍歷 modules 及嵌套 modules, 創建對應的 module 對象

    每個 module 對象 都包含各自的 stategettersactonsmutations 以及 嵌套子modules

    module 對象 會經過 children屬性(array), 收集 關聯的 子module 對象

  2. 嵌套子modules 中的 state 收集到 根modulestate 中。

    // options
    var option = {
        state: {
            name: ''
        },
        modules: {
            trade: {
                state: {
                    money: 1000
                },
                modules: {
                    loan: {
                        state: {
                            discount: 0.8
                        },
                        modules: {
                            confirm: {
                                state: {
                                    num: 2
                                }
                            }
                        }
                    },
                    product: {
                        state: {
                            discount: 0.9
                        }
                    }
                }
            }
        }
    }
    
    
    // 收集 嵌套子module state 之後的 根module state
    state: {
        name: '',
        trade: {
            loan: {
                discount: 0.8,
                confirm: {
                    num: 2
                }
            },
            product: {
                discount: 0.9
            }
        }
    }
    複製代碼
  3. 使用 new Vuestore實例 構建一個 vue實例 - _vm

    構建 vue實例 的時候, 根modulestate屬性 會做爲 data屬性 使用。

    store._vm = new Vue({
        data: {
          $$state: state  // state 爲 收集子module state 之後的 根module state
        }
     });
    複製代碼

    vue實例 構建完成之後, $$state每個屬性 都會變成一個 響應式屬性,擁有 gettersetter, 維護一個 deps列表

    vue組件 中,咱們能夠經過 this.$store.state 的方式訪問 _vm實例 中的 $$state屬性,原理以下:

    class Store {
        ...
        // 訪問 store實例的 state屬性,實質爲訪問 store._vm._data.$$state 屬性
        get state () {
            return this._vm._data.$$state
        }
    }
    複製代碼

    若是 template模板computed屬性watcher屬性 中經過 this.$store.state 的方式使用了 某一個vuex狀態, 會 觸發 vuex狀態 在 _vm.$$data 中的 同名屬性 的 getter, 對應的 watcher 會添加到 同名屬性deps列表 中。 當 修改vuex狀態 時, 觸發vuex狀態在 _vm.$$data 中的 同名屬性 的 setter,通知 deps列表 中的 watcher更新,而後 從新渲染界面返回新的計算屬性的值觸發監聽callback

getter工做原理

getter,能夠認爲是 store計算屬性。就像 vue的計算屬性 同樣, getter的返回值會被緩存,只有 依賴的state狀態 發生變化, 纔會 從新計算

getter 是基於 vue的計算屬性(computed) 實現的。

vue計算屬性實現原理 以下:

  1. 構建 vue實例 的時候,須要一個 options配置項,包含 datapropscomputedmethods 等屬性。 若是 options 中有 computed屬性,須要 遍歷computed中的屬性,爲 每個屬性 創建一個 watcher。此外,根據 每個computed屬性, 經過 defineProperty 的方式爲 vue實例 創建一個 同名屬性,設置 getter。 當經過 vue實例 訪問 計算屬性 的時候,觸發getter執行計算屬性對應的方法

  2. 每個 計算屬性,都對應一個 watcherwatcher 有一個標誌屬性: dirty。 當 dirty 屬性的值爲 true 時,獲取 計算屬性 的值, 須要 執行計算屬性對應的方法並緩存返回的結果; 當 dirty 的值爲 false 時,返回 緩存的值

    watcher初次構建 的時候,dirty 值爲默認爲 true

    執行計算屬性對應的方法 之後, dirty 的值會置爲 false

  3. 第一次 使用 計算屬性 的時候, 因爲 watcher.dirty 的值爲 true,須要 使用計算屬性對應的方法。執行方法的時候, 會讀取 響應式屬性(data、props) 的值, 觸發 響應式屬性getter方法計算屬性watcher 會被添加到 響應式屬性deps列表 中。 此外, watcher.dirty 的值會置爲 false

  4. 若是依賴的響應式屬性發生變化,觸發 setter方法, 通知 deps列表 中的 watcher 更新。 此時 watcher.dirty 的值會置爲 true下一次 使用 計算屬性 的時候, 執行計算屬性對應的方法從新計算

  5. 若是依賴的響應式屬性沒有變化watcher.dirty 的值一直爲 false下一次 使用 計算屬性 的時候,直接返回 緩存的上一次計算結果

vuexgetter, 也是在使用 new Vuex.store(options) 構建 store實例 的時候 實現 的,經歷的主要過程以下:

  1. 創建 根module子modules

  2. 子modules 中的 state 收集到 根modulestate 中。

  3. 根module子modules 中的 getters 收集到 store實例_wrappedGetters 對象中。

  4. store實例_wrappedGetters 對象做爲 computed配置項根modulestate對象 做爲 data配置 項構建 vue實例 - _vm

    store._vm = new Vue({
        data: {
          $$state: state  // state 爲 收集子module state 之後的 根module state
        },
        computed: _wrappedGetters
     });
    複製代碼
  5. store實例 添加 getters 屬性。 遍歷 _wrappedGetters 中屬性,經過 defineProperty 的方式,爲 store.getters 添加屬性,並設置 getter方法,使咱們能夠經過 store.getters.xx 的方式訪問 store._vm同名計算屬性

當咱們在 組件 中經過 this.$store.getters.xx 的方式訪問 vuex 定義的 getter 時, 等價於訪問 this.$store._vm 中的 同名計算屬性

第一次 訪問 vuex 的 getter 時,同名計算屬性 對應的 watcher.dirty 的值爲 true須要執行計算屬性對應的方法。 執行的時候, 會 讀取依賴的state的值觸發state的getter方法,而後讀取 this.$store._vm._data.$$data同名響應式屬性 的值,觸發響應式屬性的getter方法。此時,同名計算屬性watcher 會被添加到 響應式屬性的deps列表 中。同名計算屬性 對應的方法執行完畢之後,結果會 緩存watcher.dirty 的值置爲 false

若是 vuexgetter 依賴的 state 發生變化,this.$store._vm._data.$$data 中對應的 同名響應式屬性setter 會被觸發,而後 通知deps列表中的watcher更新vuexgetter同名計算屬性watcher.dirty 的值置爲 true。 下一次訪問 vuexgetter 時,根據 依賴的state,返回 新的值

若是 vuexgetter 依賴的 state 一直沒有發生變化,對應的 同名計算屬性watcher.dirty 的值一直爲 false。 下一次訪問 vuexgetter 時,返回 同名計算屬性緩存的結果

嚴格模式

使用 new Vuex.store 構建 store實例 的時候, 若是 strict配置項 的值爲 true, 則啓用 嚴格模式

啓用 嚴格模式 之後, 若是 state變動不是由 mutation 引發 的,則會拋出 異常

直接修改state經過mutation修改state, 都是 修改state,爲何 直接修改state就會拋出異常 呢?

vuex 中, 有一段源碼涉及到 嚴格模式,以下:

function enableStrictMode (store) {
  store._vm.$watch(function () { return this._data.$$state }, () => {
    {
      assert(store._committing, `do not mutate vuex store state outside mutation handlers.`);
    }
  }, { deep: true, sync: true });
}
複製代碼

構建 store實例 的時候, 咱們會爲 store實例 構建一個 vue實例_vm。 若是是 嚴格模式,執行 enableStrictMode 方法。

enableStrictMode 方法中, _vm 會監聽 $$state。 若是 $$state 發生變化,則執行 callback

不論是 直接修改state 仍是 經過mutation修改state,最後都會致使 $$state 發生變化,而後 觸發callback。 不一樣的是,使用 mutation 時,store._committing 值爲 true不會拋出異常,而 直接修改 時,store._committing 值爲 false會拋出異常

默認狀況下, 不啓用嚴格模式。 即若是構建 store實例* 的時候,未設置strict配置項不啓用嚴格模式

注意: 生產環境下, 不要啓用嚴格模式

修改 state

vuex 建議咱們經過 提交mutation 的方式 修改state, 緣由以下:

  1. 嚴格模式下(strict: ture), 若是經過 store.state.xx = xxx 的方式 修改state, 會 拋出異常

  2. 啓用dev-tools(devtools: true) 後, 若是經過 store.state.xx = xxx 的方式 修改statestate的變化 沒法被 dev-tools 追蹤。

開啓 dev-tools 之後, 當咱們經過 提交mutation 的方式 修改state 時, state的變化 能夠被 dev-tools 追蹤到。 在 dev-toolsvuex列表 中,咱們能夠清晰明瞭的看到每次 commit 操做。

構建 store實例 的時候, dev-tools 插件會經過 store.subscribe 方法 訂閱 store 的 mutation,即 註冊一個 callback。 當執行 store.commit(type, payload) 時, 先觸發 type 對應的 mutations, 而後再觸發 callback, 通知 dev-tools 追蹤 mutation。 觸發 callback時,傳入 mutation通過 mutation 後的狀態做爲參數

命名空間 - namespace

默認狀況 下,模塊內部的 actionmutationgetter註冊在全局命名空間 的,這樣使得 多個模塊 可以對 同一 mutation 或 action 做出響應。

若是 但願你的模塊具備更高的 封裝度 和 複用性,你能夠經過添加 namespaced: true 的方式使其成爲帶 命名空間 的模塊。當 模塊註冊 後,它的全部 getteractionmutation 都會 自動根據模塊註冊的路徑調整命名

命名空間 對模塊 getteractionmutation 的影響, 咱們經過一個 示例 來講明:

var option = {
    // root module
    state: { name: 'root' },
    getters: { getName: state => state.name },
    mutations: { setName: (state, name) => { state.name = name }},
    actions: { setName: (context, name) => { context.commit('setName', name) }},
    modules: {
      // module A
      moduleA: {
        namespaced: true,
        state: { name: 'moduleA' },
        getters: { getName: state => state.name },
        mutations: { setName: (state, name) => { state.name = name }},
        actions: { setName: (context, name) => { context.commit('setName', name) }}
        modules: {
          // module C
          moduleC: {
            state: { name: 'moduleC' },
            getters: { getName: state => state.name },
            mutations: { setName: (state, name) => { state.name = name }},
            actions: { setName: (context, name) => { context.commit('setName', name) }}
          },
          // module D
          moduleD: {
            namespaced: true,
            state: { name: 'moduleD' },
            getters: { getName: state => state.name },
            mutations: { setName: (state, name) => { state.name = name }},
            actions: { setName: (context, name) => { context.commit('setName', name) }}
          }
        }
      },
      // module B
      moduleB: {
        state: { name: 'moduleB' },
        getters: { getName: state => state.name },
        mutations: { setName: (state, name) => { state.name = name }},
        actions: { setName: (context, name) => { context.commit('setName', name) }}
      }
    }
  }
複製代碼

在上面的示例中,store 被切割成 root modulemoduleAmoduleBmoduleCmoduleD

root module,無論 namespced 的值爲 true 或者 false, 它的 命名空間 都是 , 即爲 全局命名空間

moduleAnamespaced 的值爲 true, 它的 命名空間'moduleA'

moduleBnamespaced 的值 不是true, 它會 繼承 父module 即 root module 的 命名空間,即它的 命名空間全局命名空間

moduleCnamespaced 的值 不是true, 它會 繼承 父module 即 moduleA 的 命名空間,即它的 命名空間'moduleA'

moduleDnamespaced 的值爲 true, 它的 命名空間'moduleA/moduleD'

命名空間gettersmutationsactions 的使用會有 影響

  • getter

    在構建 store實例 的時候,會將各個 modulegetters 收集到 store._wrappedGetters 中。

    store._wrappedGetters 是一個 對象屬性名 爲:命名空間/getter名, 對應的 屬性值是一個 函數 - 通過包裝之後的getter

    因爲 _wrappedGetters 是一個 對象, 若是 存在屬性名相同的getter,會拋出警告

    在上面的示例中, _wrappedGetters 中, 收集到的 getter 以下:

    _wrappedGetters = {
            'getName': function() {...},  // 根 module 中的 getter
            'moduleA/getName': function() {...}, // moduleA 的 getter
            'moduleA/moduleD/getName': function() {...} // moduleD 的 getter 
        }
    複製代碼

    對於 moduleB, 命名空間爲 全局命名空間,則對應的屬性名爲 getName, 此時 _wrappedGetters 中已經存在 同名屬性沒法添加且拋出警告

    moduleC 的狀況也同樣,命名空間爲 moduleA, 對應的屬性爲 moduleA/getName_wrappedGetters 中已經存在 同名屬性沒法添加且拋出警告

    組件 中,咱們能夠經過 store.getters.xx 訪問 getterxx 會對應 _wrappedGetters 中的 屬性名, 以下:

    this.$store.getters.getName  // 訪問 根module 的 getName
        
        this.$store.getters['moduleA/getName']  // 訪問 moduleA 的 getName
        
        this.$store.getters['moduleA/moduleD/getName'] // 訪問 moduleD 的 getName
    複製代碼

    moduleBmoduleCgetName 沒有收集到 _wrappedGetters 中,在 組件沒法訪問

  • mutation

    在構建 store實例 的時候,會將各個 modulemutations 收集到 store._mutations 中。

    store._mutations 是一個 對象屬性名 爲:命名空間/mutation名, 對應的 屬性值數組數組元素通過包裝的mutation

    在上面的示例中, _mutations 中, 收集到的 mutations 以下:

    _mutations: {
            // fn_root 對應 根module 的 setName, fnB 對應 moduleB 的 setName
            'setName': [fn_root, fnB],
            // fnA 對應 moduleA 的 setName, fnC 對應 moduleC 的 setName
            'moduleA/setName': [fnA, fnC],
            // fnD 對應 moduleD 的 setName
            'moduleA/moduleD/setName': [fnD]
        }
    複製代碼

    moduleB命名空間全局命名空間,對應的屬性名爲 setName, 在 _mutations 已存在, 將 mutation 添加到 setName 對應的 數組中。

    moduleC命名空間moduleA,對應的屬性名爲 moduleA/setName, 在 _mutations 已存在, 將 mutation 添加到 moduleA/setName 對應的 數組中。

    組件 中,咱們能夠經過 store.commit(type, payload) 的方式 提交mutationtype 會對應 _mutations 中的 屬性名,以下:

    // 觸發 根module、moduleB 的 setName
        this.$store.commit('setName', 'xxx') 
        // 觸發 moduleA、 moduleC 的 setName
        this.$store.commit('moduleA/setName', 'xxx')
        // 觸發 moduleD 的 setName
        this.$store.commit('moduleA/moduleD/setName', xxx)
    複製代碼

    在上面的示例中, root modulemoduleB命名空間 相同,提交 'setName' 時,root modulemoduleB 中的 mutation - setName 都會觸發; moduleAmoduleC命名空間 相同, 提交'moduleA/setName' 時, moduleAmoduleC 中的 mutation - setName 都會觸發。

  • actions

    在構建 store實例 的時候,會將各個 moduleaction 收集到 store._actions 中。

    store._actions 是一個 對象屬性名 爲:命名空間/action名, 對應的 屬性值數組數組元素通過包裝的action

    在上面的示例中, _actions 中, 收集到的 actions 以下:

    _actions: {
            // fn_root 對應 根module 的 setName, fnB 對應 moduleB 的 setName
            'setName': [fn_root, fnB],
            // fnA 對應 moduleA 的 setName, fnC 對應 moduleC 的 setName
            'moduleA/setName': [fnA, fnC],
            // fnD 對應 moduleD 的 setName
            'moduleA/moduleD/setName': [fnD]
        }
    複製代碼

    moduleB命名空間全局命名空間,對應的屬性名爲 setName, 在 _actions 已存在, 將 action 添加到 setName 對應的 數組中。

    moduleC命名空間moduleA,對應的屬性名爲 moduleA/setName, 在 _actions 已存在, 將 action 添加到 moduleA/setName 對應的 數組中。

    組件 中,咱們能夠經過 store.dispatch(type, payload) 的方式 派發 actiontype 會對應 _actions 中的 屬性名,以下:

    // 觸發 根module、moduleB 的 setName
        this.$store.dispatch('setName', 'xxx') 
        // 觸發 moduleA、 moduleC 的 setName
        this.$store.dispatch('moduleA/setName', 'xxx')
        // 觸發 moduleD 的 setName
        this.$store.dispatch('moduleA/moduleD/setName', xxx)
    複製代碼

    在上面的示例中, root modulemoduleB命名空間 相同,派發 'setName' 時,root modulemoduleB 中的 action - setName 都會觸發; moduleAmoduleC命名空間 相同, 派發 'moduleA/setName' 時, moduleAmoduleC 中的 action - setName 都會觸發。

state的使用

一般,咱們會在 vue組件 中經過 計算屬性 來訪問所需的 狀態 - state

具體的方式,咱們經過一個 示例 來講明。

var options = {
    state: {
        name: 'zhangsan'
    },
    modules: {
        trade: {
            namespaced: true,
            state: {
                type: 'waiting'
            },
            modules: {
                product: {
                    namespaced: true,
                    state: {
                        id: 1
                    }
                }
            }
        },
        loan: {
            namespaced: true,
            state: {
                money: 100000
            }
        }
    }
}
複製代碼
  • 直接訪問

    咱們能夠經過 vm.$store.state.xx 的方式直接訪問所需的 狀態 - state

    var bill = {
        data() {
            return {...}
        },
        computed: {
            name() {
                return this.$store.state.name
            },
            tradeType() {
                return this.$store.state.trade.type
            },
            productId() {
                return this.$store.state.trade.product.id  
            },
            loanMoney() {
                return parseInt(this.$store.state.loan.money / 1000)
            }
        }
    }
    複製代碼
  • 經過 mapState 輔助函數

    咱們可使用 輔助函數 - mapState 幫助咱們生成 計算屬性

    var mapState = Vuex.mapState
    
    var bill = {
        data() {
            return {...}
        },
        computed: {
            ...mapState({   // 全局命名空間
                name: 'name',
                tradeType: state => state.trade.type // 全局 state
            }),
            ...mpaState('trade/product', { // trade/product 命名空間
                productId: 'id'
            }),
            ...mapState('loan', {  // loan 命名空間
                loanMoney: state => parseInt(state.money / 1000) // 局部 state
            })
        }
    }
    複製代碼
  • 使用 createNamespacedHelpers 輔助函數

    咱們可使用 輔助函數 - createNamespacedHelpers、mapState 幫助咱們生成 計算屬性

    createNamespacedHelpers 能夠幫助咱們生成 基於命名空間輔助函數

    // 基於 全局 命名空間 的 mapState
    var mapState = Vuex.mapState
    // 基於 trade 命名空間 的 mapState
    var tradeMapState = Vuex.createNamespacedHelpers('trade').mapState
    // 基於 trade/product 命名空間 的 mapState
    var productMapState = Vuex.createNamespacedHelpers('trade/product').mapState
    // 基於 loan 命名空間的 mapState 
    var loanMapState = Vuex.createNamespacedHelpers('loan').mapState
    
    var bill = {
        data() {
            return {...}
        },
        computed: {
            ...mapState(['name']), // 計算屬性名爲 name, 返回值爲 this.$store.state.name
            ...tradeMapState({
                tradeType: 'type' // 計算屬性名爲 tradeType, 返回值爲 this.$store.state.trade.type
            }),
            ...productMapState({
                productId: 'id'
            }),
            ...mapState({
                loanMoney: state => parseInt(state.money / 1000)
            })
        }
    }
    複製代碼

getter的使用

vuexgetter 的使用,和 state 相似,咱們一樣以一個示例來講明。

var options = {
    state: {
        name: 'zhangsan'
    },
    getters: {
        getName(state) {
            return state.name
        }
    },
    modules: {
        trade: {
            namespaced: true,
            state: {
                type: 'waiting'
            },
            getters: {
              getTradeType(state) {
                  return state.type
              }  
            },
            modules: {
                product: {
                    namespaced: true,
                    state: {
                        id: 1
                    },
                    getters: {
                        getProductId(state) {
                            return state.id
                        }
                    }
                }
            }
        },
        loan: {
            namespaced: true,
            state: {
                money: 100000
            },
            getters: {
                getLoanMoney(state) {
                    return state.money
                }
            }
        },
        confirm: {
            state: {
                num: 100
            },
            getters: {
                getConfirmNum(state) {
                    return state.num
                }
            }
        }
    }
}
複製代碼
  • 直接使用

    咱們能夠經過 vm.$store.getters.xx 的方式直接訪問所需的 getter

    var mapGetters = Vuex.mapGetters
    
    var bill = {
        data() {
            return {...}
        },
        computed: {
            getName() {
                return this.$store.getters.getName
            },
            getTradeType() {
                return this.$store.getters['trade/getTradeType']
            },
            getLoanMoney() {
                return this.$store.getters['loan/getLoanMoney']
            },
            getProductId() {
                return this.$store.getters['trade/product/getProductId']
            },
            getConfirmNum() {
                return this.$store.getters.getConfirmNum
            }
        },
        ...
    }
    複製代碼
  • 使用 mapGetters 輔助函數

    咱們可使用 輔助函數 - mapGetters 幫助咱們生成 計算屬性

    var bill = {
        data() {
            return {...}
        },
        computed: {
            ...mapGetters(['getName', 'getConfirmNum']),
            ...mapGetters({
                getTradeType: 'trade/getTradeType'
            }),
            ...mapGetters('trade', {
                getProductId: 'product/getProductId'
            }),
            ...mapGetters('loan', {
                'getLoanMoney': 'getLoanMoney'
            })
        },
        ...
    }
    複製代碼
  • 使用 createNamespacedHelpers 輔助函數

    咱們可使用 輔助函數 - createNamespacedHelpers、mapGetters 幫助咱們生成 計算屬性

    createNamespacedHelpers 能夠幫助咱們生成 基於命名空間輔助函數

    // 基於 全局 命名空間 的 mapGetters
    var mapGetters = Vuex.mapGetters
    // 基於 trade 命名空間 的 mapGetters
    var tradeMapGetters = Vuex.createNamespacedHelpers('trade').mapGetters
    // 基於 trade/product 命名空間 的 mapGetters
    var productMapGetters = Vuex.createNamespacedHelpers('trade/product').mapGetters
    // 基於 loan 命名空間的 mapGetters 
    var loanMapGetters = Vuex.createNamespacedHelpers('loan').mapGetters
    
    var bill = {
        data() {
            return {...}
        },
        computed: {
            ...mapGetters(['getName', 'getConfirmNum']),
            ...tradeMapGetters({
                getTradeType: 'getTradeType'
            }),
            ...productMapGetters(['getProductId']),
            ...loanMapGetters(['getLoanMoney'])
        }
    }
    複製代碼

mutation的使用

mutation 用於 更改 vuex 的 state, 它的用法和 stategetter 相似,咱們一樣以一個示例來講明。

var options = {
    state: {
        name: 'zhangsan'
    },
    mutations: {
        setName(state, name) {
            state.name = name
        }
    },
    modules: {
        trade: {
            namespaced: true,
            state: {
                type: 'waiting'
            },
            mutations: {
                setTradeType(state, type) {
                    state.type = type
                }
            }
            modules: {
                product: {
                    namespaced: true,
                    state: {
                        id: 1
                    },
                    mutations: {
                        setProductId(state, id) {
                            state.id = id
                        }
                    }
                }
            }
        },
        loan: {
            namespaced: true,
            state: {
                money: 100000
            },
            mutations: {
                setLoanMoney(state, money) {
                    state.money = money
                }
            }
        },
        confirm: {
            state: {
                num: 10
            },
            mutations: {
                setConfirmNum(state, num) {
                    state.num = num
                }
            }
        }
    }
}
複製代碼
  • 直接使用

    組件中,咱們能夠經過 this.$store.commit(type, payload) 的方式提交 mutation,觸發 state狀態更改

    var bill = {
        ...
        methods: {
            setName() { 
                this.$store.commit('setName', '李四') 
            },
            setTradeType() { 
                this.$store.commit('trade/setTradeType', 'complete') 
            },
            setProductId() {
                this.$store.commit('trade/product/setProductId', '2')
            },
            setLoanMoney() {
                this.$store.commit('loan/setLoanMoney', '2000000')
            },
            setConfirmNum() {
                this.$store.commit('setConfirmNum', '20')
            }
        }
    }
    複製代碼
  • 使用 mapMutations 輔助函數

    咱們可使用 輔助函數 - mapMutations 幫助咱們生成 組件方法 來提交 mutation,觸發 state狀態更改

    var mapMutations = Vuex.mapMutations
    
    var bill = {
        ...
        methods: {
            // 給 bill組件 添加 setName 方法, 映射 this.$store.commit('setName')
            // 給 bill組件 添加 setConfirmNum 方法, 映射 this.$store.commit('setConfirmNum')
            ...mapMutations(['setName', 'setConfirmNum']),
            // 給 bill組件 添加 setTradeType 方法, 映射 this.$store.commit("trade/setTradeType') ...mapMutations({ setTradeType: 'trade/setTradeType' }), // 給 bill組件 添加 setProductId, 映射 this.$store.commit('trade/product/setProductId') ...mapMutations('trade', { setProductId: 'product/setProductId' }), // 給 bill組件 添加 setLoanMoney, 映射 this.$store.commit('loan/setLoanMoney') ...mapMutations('loan', { setLoanMoney: function(commit, money) { commit('setLoanMoney', money) } }), submit() { // this.$store.commit('setName', '李四') this.setName('李四') // this.$store.commit('setConfirmNum', 20) this.setConfirmNum(20) // this.$store.commit('trade/setTradeType', 'complete') this.setTradeType('complete') // this.$store.commit('trade/product/setProductId', 2345) this.setProductId('2345') // this.$store.commit('loan/setLoanMoney', 2000000) this.setLoanMoney(20000000) } } } 複製代碼
  • 使用 createNamespacedHelpers 輔助函數

    咱們可使用 輔助函數 - createNamespacedHelpers、mapMutations 幫助咱們生成 生成 組件方法 來提交 mutation,觸發 state狀態更改。。

    createNamespacedHelpers 能夠幫助咱們生成 基於命名空間輔助函數

    // 基於 全局 命名空間 的 mapMutations
    var mapMutations = Vuex.mapMutations
    // 基於 trade 命名空間 的 mapMutations
    var tradeMapMutations = Vuex.createNamespacedHelpers('trade').mapMutations
    // 基於 trade/product 命名空間 的 mapMutations
    var productMapMutations = Vuex.createNamespacedHelpers('trade/product').mapMutations
    // 基於 loan 命名空間的 mapMutations 
    var loanMapMutations = Vuex.createNamespacedHelpers('loan').mapMutations
    
    var bill = {
        ...
        methods: {
            // 給 bill組件 添加 setName 方法, 映射 this.$store.commit('setName')
            // 給 bill組件 添加 setConfirmNum 方法, 映射 this.$store.commit('setConfirmNum')
            ...mapMutations(['setName', 'setConfirmNum']),
            // 給 bill組件 添加 setTradeType 方法, 映射 this.$store.commit('trade/setTradeType')
            ...tradeMapMutations(['setTradeType']),
            // 給 bill組件 添加 setProductId, 映射 this.$store.commit('trade/product/setProductId')
            ...productMapMutations(['setProductId']),
            // 給 bill組件 添加 setLoanMoney, 映射 this.$store.commit('loan/setLoanMoney') 
            ...loanMapMutations({
                setLoanMoney: function(commit, money) {
                    commit('setLoanMoney', money)
                }
            }),
            submit() {
                // this.$store.commit('setName', '李四')
                this.setName('李四')
                // this.$store.commit('setConfirmNum', 20)
                this.setConfirmNum(20)
                // this.$store.commit('trade/setTradeType', 'complete')
                this.setTradeType('complete')
                // this.$store.commit('trade/product/setProductId', 2345)
                this.setProductId('2345')
                // this.$store.commit('loan/setLoanMoney', 2000000) 
                this.setLoanMoney(20000000)
            }
        }
    }
    複製代碼

action的使用

action 相似於 mutation,不一樣之處在於:action 提交的是 mutation,而 不是直接變動狀態action 能夠 包含任意異步操做

action組件 中的使用, 和 mutation 相似,咱們經過一個 示例 來講明:

var options = {
    state: {
        name: 'zhangsan'
    },
    mutations: {
        setName(state, name) {
            state.name = name
        }
    },
    actions: {
        setName(context, name) {
            setTimeout(() => {
                context.commit('setName', name)
            }, 0)
        }
    }
    modules: {
        trade: {
            namespaced: true,
            state: {
                type: 'waiting'
            },
            mutations: {
                setTradeType(state, type) {
                    state.type = type
                }
            }
            actions: {
                setTradeType(context, type) {
                    setTimeout(() => {
                        context.commit('setTradeType', type)
                    }, 1500)
                }
            }
            modules: {
                product: {
                    namespaced: true,
                    state: {
                        id: 1
                    },
                    mutations: {
                        setProductId(state, id) {
                            state.id = id
                        }
                    },
                    actions: {
                        setProductId(context, id) {
                            setTimeout(() => {
                                context.commit('setProductId', id)
                            }, 3000)
                        }
                    }
                }
            }
        },
        loan: {
            namespaced: true,
            state: {
                money: 100000
            },
            mutations: {
                setLoanMoney(state, money) {
                    state.money = money
                }
            },
            actions: {
                setLoanMoney(context, money) {
                    setTimeout(() => {
                        context.commit('setLoanMoney', money)
                    }, 2000)
                }
            }
        },
        confirm: {
            state: {
                num: 10
            },
            mutations: {
                setConfirmNum(state, num) {
                    state.num = num
                }
            },
            actions: {
                setConfirmNum(context, num) {
                    setTimeout(() => {
                        context.commit('setConfirmNum', num)
                    }, 5000)
                }
            }
        }
    }
}
複製代碼
  • 直接使用

    在組件中,咱們能夠直接經過 this.$store.dispatch(type, payload) 的方式 派發 action,觸發 mutaion提交

    var bill = {
        ...
        methods: {
            setName() { 
                this.$store.dispatch('setName', '李四') 
            },
            setTradeType() { 
                this.$store.dispatch('trade/setTradeType', 'complete') 
            },
            setProductId() {
                this.$store.dispatch('trade/product/setProductId', '2')
            },
            setLoanMoney() {
                this.$store.dispatch('loan/setLoanMoney', '2000000')
            },
            setConfirmNum() {
                this.$store.dispatch('setConfirmNum', '20')
            }
        }
    }
    複製代碼
  • 使用 mapActions 輔助函數

    咱們可使用 輔助函數 - mapActions 幫助咱們生成 組件方法 來派發 action,觸發 mutation提交

    var mapActions = Vuex.mapActions
    
    var bill = {
        ...
        methods: {
            // 給 bill組件 添加 setName 方法, 映射 this.$store.dispatch('setName')
            // 給 bill組件 添加 setConfirmNum 方法, 映射 this.$store.dispatch('setConfirmNum')
            ...mapActions(['setName', 'setConfirmNum']),
            // 給 bill組件 添加 setTradeType 方法, 映射 this.$store.dispatch("trade/setTradeType') ...mapActions({ setTradeType: 'trade/setTradeType' }), // 給 bill組件 添加 setProductId, 映射 this.$store.dispatch('trade/product/setProductId') ...mapActions('trade', { setProductId: 'product/setProductId' }), // 給 bill組件 添加 setLoanMoney, 映射 this.$store.dispatch('loan/setLoanMoney') ...mapActions('loan', { setLoanMoney: function(dispatch, money) { dispatch('setLoanMoney', money) } }), submit() { // this.$store.dispatch('setName', '李四') this.setName('李四') // this.$store.dispatch('setConfirmNum', 20) this.setConfirmNum(20) // this.$store.dispatch('trade/setTradeType', 'complete') this.setTradeType('complete') // this.$store.dispatch('trade/product/setProductId', 2345) this.setProductId('2345') // this.$store.dispatch('loan/setLoanMoney', 2000000) this.setLoanMoney(20000000) } } } 複製代碼
  • 使用 createNamespacedHelpers 輔助函數

    咱們可使用 輔助函數 - createNamespacedHelpers、mapActions 幫助咱們生成 生成 組件方法 來派發 action,觸發 mutation提交。。

    createNamespacedHelpers 能夠幫助咱們生成 基於命名空間輔助函數

    // 基於 全局 命名空間 的 mapActions
    var mapActions = Vuex.mapActions
    // 基於 trade 命名空間 的 mapActions
    var tradeMapActions = Vuex.createNamespacedHelpers('trade').mapActions
    // 基於 trade/product 命名空間 的 mapActions
    var productMapActions = Vuex.createNamespacedHelpers('trade/product').mapActions
    // 基於 loan 命名空間的 mapActions 
    var loanMapActions = Vuex.createNamespacedHelpers('loan').mapActions
    
    var bill = {
        ...
        methods: {
            // 給 bill組件 添加 setName 方法, 映射 this.$store.dispatch('setName')
            // 給 bill組件 添加 setConfirmNum 方法, 映射 this.$store.dispatch('setConfirmNum')
            ...mapActions(['setName', 'setConfirmNum']),
            // 給 bill組件 添加 setTradeType 方法, 映射 this.$store.dispatch('trade/setTradeType')
            ...tradeMapActions(['setTradeType']),
            // 給 bill組件 添加 setProductId, 映射 this.$store.dispatch('trade/product/setProductId')
            ...productMapActions(['setProductId']),
            // 給 bill組件 添加 setLoanMoney, 映射 this.$store.dispatch('loan/setLoanMoney') 
            ...loanMapActions({
                setLoanMoney: function(dispatch, money) {
                    dispatch('setLoanMoney', money)
                }
            }),
            submit() {
                // this.$store.dispatch('setName', '李四')
                this.setName('李四')
                // this.$store.dispatch('setConfirmNum', 20)
                this.setConfirmNum(20)
                // this.$store.dispatch('trade/setTradeType', 'complete')
                this.setTradeType('complete')
                // this.$store.dispatch('trade/product/setProductId', 2345)
                this.setProductId('2345')
                // this.$store.dispatch('loan/setLoanMoney', 2000000) 
                this.setLoanMoney(20000000)
            }
        }
    }
    複製代碼

其餘

  1. 不能經過 store.state = xx 方式直接修改 state, 不然會 拋出異常

    class Store {
        ...
        // 訪問 store實例的 state屬性,實質爲訪問 store._vm._data.$$state 屬性
        get state () {
            return this._vm._data.$$state
        }
        set state (v) {
            {
              assert(false, `use store.replaceState() to explicit replace store state.`);
            }
        }
        ...
        replaceState (state) {
            this._withCommit(() => {
              this._vm._data.$$state = state;
            });
        }
    }
    複製代碼

    爲何不能在 set state 中, 使用 replaceState中的方式修改 state?? 暫未理解。

  2. 經過 store.watch 能夠 響應式地偵聽 fn 的返回值,當值改變時 調用callback

    fn 接收 storestate 做爲第一個參數,其 getter 做爲第二個參數, 只要 vuex狀態 發生 變化fn 都會執行, 若是 返回的值和上一次不同, 觸發 callback

  3. 經過 store.subscribe 方法能夠 訂閱 store 的 mutation。 當咱們經過 store.commit(type, payload) 的方式提交 mutation 時, 先觸發 type 對應的 mutation, 再觸發 subscribe 註冊的 handler

    vue-devtools 就是利用 store.subscribe 來追蹤 應用中提交的 mutation

    store.subscribe((mutation, state) => {
        // vue-devtools 提供的 devtoolHook
        devtoolHook.emit('vuex:mutation', mutation, state);
    });
    複製代碼

    store.subscribe 通常被 插件 調用。

  4. 經過 store.subscribeAction 方法能夠訂閱 storeaction。 用法以下:

    store.subscribeAction({
        before: function(action, state) {
            ...
        },
        after: function(action, state) {
            ...
        }
    })
    複製代碼

    當咱們經過 store.dispatch(type, payload) 的方式派發 action 時, 先觸發 before 對應的 handler,再觸發 type 對應的 mutation, 最後觸發 after 對應的 handler

    若是 subscribeAction 的參數是一個 函數, 默認當作 before 對應的 handler, 即:

    store.subscribeAction(function(action, state) {
        ...
    })
    複製代碼
  5. 經過 store.registerModule 方法能夠 動態註冊一個 新模塊

    registerModule第一個參數,表明 註冊的模塊名及所在的父module第二個參數註冊module須要的配置項, 即 { namespaced, state, getters, actions, mutations, modules }

    // 在 根module 中註冊 moduleA
    store.registerModule('moduleA', {...})
    // 在 根module 中註冊 moduleA
    store.registerModule(['moduleA'], {...})
    // 在 moduleB 中註冊 moduleC
    store.registerModule(['moduleA', 'moduleB', 'moduleC'],  {...})
    複製代碼

    注意,在第三個示例中, 若是 moduleA根module 中不存在, moduleBmoduleA 中不存在, 會拋出 異常

    動態註冊一個新模塊之後, store實例會從新收集state、getters、mutations、actions, 從新爲store實例構建vm實例(響應式原理)

  6. 經過 store.unregisterModule 方法能夠卸載一個動態模塊。

    unregisterModule 只有 一個參數, 表明須要 卸載的模塊及所在的父module

    // 在 根module 中卸載 moduleA
    store.unregisterModule('moduleA')
    // 在 根module 中卸載 moduleA
    store.unregisterModule(['moduleA'])
    // 在 moduleB 中卸載 moduleA
    store.unregisterModule(['moduleA', 'moduleB', 'moduleC'])
    複製代碼

    卸載的模塊必須是動態註冊的模塊,不然拋出異常。 緣由是: 卸載靜態module 時, 靜態module 不會從 父module 中移除, 可是 靜態module 對應的 state 會被移除, 若是 vue組件 中有使用 靜態module 中的 state,會報錯。 這裏應該是vux的一個bug吧。

    若是 要卸載的模塊在父模塊中不存在,會 報錯。 這裏應該也是一個bug。

    若是 要卸載的模塊的父模塊的路勁不對, 會 報錯

    卸載一個動態註冊的模塊之後, store實例會從新收集state、getters、mutations、actions, 從新爲store實例構建vm實例(響應式原理)

  7. devtools 配置項

    爲 store實例 打開或者關閉 devtools

    若是將 devtools 設置爲 falsevue-devtools 就沒法經過 store.subscribe 方法 訂閱 store 的 mutation沒法追蹤store提交的mutation

相關文章
相關標籤/搜索