vuex 基本入門和使用(三)-關於 mutation

vuex 版本爲^2.3.1,按照我本身的理解來整理vuex。html

關於 mutation

這裏應該很好理解。vue

更改 Vuex 的 store 中的狀態的惟一方法是提交 mutation。Vuex 中的 mutation 很是相似於事件:每一個 mutation 都有一個字符串的 事件類型 (type) 和 一個 回調函數 (handler)。這個回調函數就是咱們實際進行狀態更改的地方,而且它會接受 state 做爲第一個參數vuex

const store = new Vuex.Store({
  state: { // 相似 vue 的 data
    count: 1
  },
  mutations: { // 相似 vue 的 methods
    increment (state) { // 這是一個回調函數
      // 變動狀態
      state.count++
    }
  }
})
複製代碼

你不能直接調用一個 mutation handler。這個選項更像是事件註冊:「當觸發一個類型爲 increment 的 mutation 時,調用此函數。」要喚醒一個 mutation handler,你須要以相應的 type 調用store.commit 方法:segmentfault

// 至關於就是一個特殊的調用事件方式來調用
store.commit('increment')
複製代碼

提交載荷(Payload)

能夠向 store.commit 傳入額外的參數,即 mutation 的 載荷(payload)api

mutations: {
 // 第一個參數是 state,第二個參數叫額外的參數,這裏是n
  increment (state, n) {
    state.count += n
  }
}
// 回調函數 increment 和參數10,後者是做爲額外參數傳入,n 就是10
store.commit('increment', 10)
複製代碼

在大多數狀況下,載荷應該是一個對象,這樣能夠包含多個字段而且記錄的 mutation 會更易讀:數組

mutations: {
  increment (state, payload) {
	  // payload 做爲一個對象,更加可讀,統一對象形式調用
    state.count += payload.amount
  }
}
// 傳入的是對象(即將額外的 mutation 參數以對象的方式傳入)
store.commit('increment', {
  amount: 10
})
複製代碼

這裏總的來講就是說 mutations 能夠傳參數,而且參數最好以對象的方式來傳。app

對象風格的提交方式

提交 mutation 的另外一種方式是直接使用包含 type 屬性的對象:異步

// 這裏也是傳入一個對象,不過這個對象包含了 type 屬性
store.commit({
  type: 'increment',
  amount: 10
})
複製代碼

這裏只是一種提交 mutations 的方式,沒必要深究。函數

當使用這種對象風格的提交方式,整個對象都做爲載荷傳給 mutation 函數,所以 handler 保持不變:工具

mutations: {
  increment (state, payload) {
    state.count += payload.amount
  }
}
// vuex 會將這個對象分解,除了 type 以外的,依然會是做爲額外參數傳入
store.commit({
  type: 'increment',
  amount: 10
})
複製代碼

將整個對象傳給 mutation後,vuex 會根據 type 參數識別到這是一個mutation 的載荷參數,而後自動填充 state 參數爲第一位,第二位參數爲傳入的這個對象的第二位參數。

這是 jsrun 的 demo 例子:jsrun.net/VvqKp

例子裏面會變成加11 !

Mutation 需遵照 Vue 的響應規則

既然 Vuex 的 store 中的狀態是響應式的,那麼當咱們變動狀態時,監視狀態的 Vue 組件也會自動更新。這也意味着 Vuex 中的 mutation 也須要與使用 Vue 同樣遵照一些注意事項:

  • 最好提早在你的 store 中初始化好全部所需屬性。
  • 當須要在對象上添加新屬性時,你應該
    • 使用 Vue.set(obj, 'newProp', 123) (沿用 vue 的方式)
    • 以新對象替換老對象。例如,利用 stage-3 的對象展開運算符咱們能夠這樣寫:state.obj = { ...state.obj, newProp: 123 }(先用擴展符號解構對象,而後賦值到新對象,由於對象在 js 裏面是引用類型。)

使用常量替代 Mutation 事件類型

使用常量替代 mutation 事件類型在各類 Flux 實現中是很常見的模式。這樣可使 linter 之類的工具發揮做用,同時把這些常量放在單獨的文件中可讓你的代碼合做者對整個 app 包含的 mutation 一目瞭然:

用不用常量取決於你——在須要多人協做的大型項目中,這會頗有幫助。但若是你不喜歡,你徹底能夠不這樣作。

// mutation-types.js 放置常量的文件
export const SOME_MUTATION = 'SOME_MUTATION'

// store.js
import Vuex from 'vuex'
// 單獨導入了某個常量來測試這個用法
import { SOME_MUTATION } from './mutation-types'

const store = new Vuex.Store({
  state: { ... },
  mutations: {
    // 咱們可使用 ES2015 風格的計算屬性命名功能來使用一個常量做爲函數名
    [SOME_MUTATION] (state) {
      // mutate state
    }
  }
})
複製代碼

備註:es2015的計算屬性名會使用中括號進行命名,中括號的方式容許咱們使用變量或者在使用標識符時會致使語法錯誤的字符串直接量來定義屬性,例如person["first name"],僅此而已。

我以爲,及早適應這種寫法比較好,既能裝逼又能夠學到別人的高級技能。

Mutation 必須是同步函數

一條重要的原則就是要記住 mutation 必須是同步函數。

實質上任何在回調函數中進行的的狀態的改變都是不可追蹤的。因此須要在 actions 裏面進行異步封裝 mutation 來實現異步。

在組件中提交 Mutation

你能夠在組件中使用 this.$store.commit('xxx')提交 mutation,或者使用 mapMutations 輔助函數將組件中的 methods 映射爲 store.commit調用(須要在根節點注入 store)。

import { mapMutations } from 'vuex'

export default {
  // ...
  methods: {
    // mapMutations 工具函數會將 store 中的 commit 方法映射到組件的 methods 中
    ...mapMutations([
      'increment', // 將 `this.increment()` 映射爲 `this.$store.commit('increment')`
      // `mapMutations` 也支持載荷:
      'incrementBy' // 將 `this.incrementBy(amount)` 映射爲 `this.$store.commit('incrementBy', amount)`
    ]),
    ...mapMutations({
      add: 'increment' // 將 `this.add()` 映射爲 `this.$store.commit('increment')`
    })
  }
}
複製代碼
  • ...es2015的擴展運算符,可以解構數組或者對象,這裏是解構mapMutations對象。
  • mapMutations的寫法和參數:mapMutations(namespace?: string, map: Array<string> | Object): Object(官網 api 文檔的格式)
    • 第一個參數是模塊的空間名稱字符串,能夠不填
    • 第二個參數是一個 map結構的對象,也能夠是字符串數組
    • 返回的是一個對象
    • 瞭解更多 jsdoc 的格式標註,請參考:Use JSDoc: @type

關於...mapMutations

首先:normalizeMap會將mutations格式化爲一個數組:

function normalizeMap (map) {
  // 判斷是否數組,而且最終返回也是一個數組
  return Array.isArray(map)
    // 是數組就直接 map 循環
    ? map.map(key => ({ key, val: key }))
    // 是對象就將 key拿出來,而後再進行 map 循環
    : Object.keys(map).map(key => ({ key, val: map[key] }))
}
複製代碼

例如傳入的mutations 是一個數組,以下:

// 轉換前
[
      // 這是沒額外參數的(沒載荷)
      'increment',
      // 這是有額外參數的(有載荷)
      'incrementBy' 
]
// 那麼被normalizeMap轉換後:
// 即轉換爲{ key, val: key })
[
    { 
     key, // key 是increment
     val: key // val是increment
    },
    // 這裏雖說有額外參數傳入,可是這個參數並無在轉換中處理
    { 
     key, // key 是incrementBy
     val: key // val是incrementBy
    },    
    //.....
]
複製代碼

例如傳入的mutations 是一個對象,以下:

// 轉換前
{
      addAlias: function(commit, playload) {
           commit('increment') 
           commit('increment', playload)
      } 
}
// 那麼被normalizeMap轉換後:
// 即轉換爲{ key, val: key })
{ 
    key, // key 是addAlias
    val: map[key] // val 是對象的 key 屬性的值,就是 function().....
}
複製代碼

而後看回去 vuex 的源代碼關於mapMutations的部分:

// 參考 vuex 的源代碼
var mapMutations = normalizeNamespace(function (namespace, mutations) {
  var res = {};
  // 被normalizeMap格式化後的mutations被 foreach 循環
  normalizeMap(mutations).forEach(function (ref) {
    var key = ref.key;
    var val = ref.val;

    res[key] = function mappedMutation () {
      // 拷貝載荷:複製額外參數到 args 數組
      var args = [], len = arguments.length;
      while ( len-- ) args[ len ] = arguments[ len ];
        
      var commit = this.$store.commit;
      // 先無論命名空間
      //......
      return typeof val === 'function'
        // 是函數,則直接執行該函數,並將comit做爲其第一個參數,arg仍然做爲後續參數。
        ? val.apply(this, [commit].concat(args))
        // 不是函數,則直接執行commit,參數是value和載荷組成的數組。
        : commit.apply(this.$store, [val].concat(args))
    };
  });
  return res
});
複製代碼
  • 和mapState的實現幾乎徹底同樣,惟一的差異只有兩點:
    • 提交mutaion時能夠傳遞載荷,也能夠不傳,無論傳不傳,這裏都會進行拷貝載荷。
    • 對於函數就會執行這個函數,由於這個函數裏面其實就是一些 commit,而對於不是函數的內容就會直接進行 commit 操做,不過會綁定當前的this.$store做爲做用域,也會傳載荷的參數。

那麼迴歸到實際轉換效果,以下:

// 須要引入mapMutations纔可使用
import { mapMutations } from 'vuex' 

export default {
  // ...
  methods: {
    ...mapMutations('moduleName', [
      // 將 `this.increment()` 映射爲 `this.$store.commit('increment')`
      'increment',

      // `mapMutations` 也支持載荷:
      // 將 `this.incrementBy(amount)` 映射爲 `this.$store.commit('incrementBy', amount)`
      'incrementBy' 
    ]),
    
    ...mapMutations('moduleName', {
      // 將 `this.add()` 映射爲 `this.$store.commit('increment')`
      add: 'increment' 
    }),
    
    ...mapMutations('moduleName', {
      addAlias: function(commit) {
          //將 `this.addAlias()` 映射爲 `this.$store.commit('increment')`
          commit('increment') 
      }
    })
  }
}
複製代碼
  • increment和incrementBy實際上是同樣的,只是爲了區分因此起了兩個名字,能夠看到他是直接轉爲爲this.$store.commit('increment')的,有參數的話會自動加參數而已。
  • 其餘就比較好理解了,結合以前看到的源碼,就知道他的轉換是分別處理

這是 jsrun 的例子:jsrun.net/U6qKp


參考:

相關文章
相關標籤/搜索