vuex 基本入門和使用(二)

vuex 基本入門和使用(二)

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

關於 state

每一個vuex 應用只有一個 store 實例,因此使用起來不會太複雜,對於定位錯誤狀態和操做會很方便。vue

簡單用法:在vuex 的計算屬性中返回vuex 的狀態

最基本的使用方式,經過在vue文件裏面初始化 vuex 的 store 來進行操做 vuex 的數據:以下例子:git

// 在組件裏面使用 vuex
// 初始化 vuex 實例 
const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
      increment: state => state.count++,
    decrement: state => state.count--
  }
})

// 建立一個 Counter 組件
const Counter = {
  template: `<div>{{ count }}</div>`,
  computed: {
    count () {
        // 直接返回 state
      return store.state.count
    }
  }
}

// 初始化 vue 實例
const app = new Vue({
  el: '#app',
  components: { Counter },
  template: `
    <div class="app">
     <button @click="increment">+</button>
     <button @click="decrement">-</button> 
     <counter></counter>
    </div>
  `
  ,
   methods: {
    increment () {
      store.commit('increment')
    },
    decrement () {
        store.commit('decrement')
    }
  }
})
  • 每當 store.state.count 變化的時候, 都會從新求取計算屬性,而且觸發更新相關聯的 DOM。
  • 然而,這種模式致使組件依賴全局狀態單例。在模塊化的構建系統中,在每一個須要使用 state 的組件中須要頻繁地導入,而且在測試組件時須要模擬狀態。
我覺得,當項目發展到多個模塊,多個組件和子組件混合的時候,在這種場合,單純的值傳遞方式會很麻煩,由於組件或者模塊之間的變量是獨立的,對於一些全局使用的屬性相似 token,cookie 之類的東西,或者是一些多個模塊之間共享的屬性。

因此 vuex 提供一個新的方式來將 vuex 的 store 存放到根組件下,經過 store 選項,將store從根組件「注入」到每個子組件中(需調用 Vue.use(Vuex)):es6

// 初始化 vuex的 store(能夠將這個放到其餘文件裏面去)
const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
      increment: state => state.count++,
    decrement: state => state.count--
  }
})

// 在初始化 vue的時候註冊 store(store 便是 vuex 的 store)
const app = new Vue({
  el: '#app',
  // 把 store 對象提供給 「store」 選項,這能夠把 store 的實例注入全部的子組件
  store,
  components: { Counter }, // 子組件
  template: `
    <div class="app">
      <counter></counter>
    </div>
  `
})

經過在根實例中註冊 store 選項,該 store 實例會注入到根組件下的全部子組件中,且子組件能經過 this.$store 訪問到。讓咱們更新下 Counter 的實現:github

// 這是子組件 Counter
const Counter = {
  template: `<div>{{ count }}</div>`,
  computed: {
    count () {
        //  經過this.$store可以訪問到 store 而且獲取到 state
      return this.$store.state.count
    }
  }
}

經過這種方式能夠在實際應用中,將 vuex 的初始化分離出去其餘模塊化文件,而後在 vue初始化的引用相關 vuex 的文件便可,而後經過this.$store全局調用 vuex 的 store 來實現數據存儲。vuex

我整理了一下完整的例子,這是 jsrun的例子:
http://jsrun.net/qWqKpsegmentfault

高級用法:mapState 輔助函數

當一個組件須要獲取多個狀態時候,將這些狀態都聲明爲計算屬性會有些重複和冗餘。爲了解決這個問題,咱們可使用 mapState 輔助函數幫助咱們生成計算屬性,讓你少按幾回鍵。(其實就是自動轉換了一些語法輸出)數組

import { mapState } from 'vuex'須要先引入纔可使用

mapState使用先後對比:緩存

// 不使用mapState時:
computed: {
    count () {
      return this.$store.state.count
    }
  }
// 使用mapState時:
computed: mapState({
       count: state => state.count,
})

若是在大批量的相似這種的計算屬性的話,使用 mapState 會更加便捷,並且不僅是普通字符串,函數也能夠轉換,確實是比較方便的。cookie

這裏會有一個疑問,mapState到底作了什麼事情,怎麼將代碼轉爲 vue 能識別的代碼?因此須要參考 vuex 源代碼中關於mapState的部分:(我找了當前 vuex 版本3.01的源代碼:https://github.com/vuejs/vuex/blob/e821f1bf5b818992d7f34c03029ce2ded1f52a75/dist/vuex.esm.jsapp

這是normalizeMap的代碼:

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] }))
}

這是mapState的代碼:

var mapState = normalizeNamespace(function (namespace, states) {
  var res = {}; // 這是一個對象類型
  // 將 state 經過normalizeMap格式化變成一個數組,數組元素是一個對象
  normalizeMap(states).forEach(function (ref) {
    var key = ref.key;// 將數組元素對象解出來,先保存起來被後面使用
    var val = ref.val;
    // 組成一個新數組,以 key 爲 key,值是一個函數mappedState
    res[key] = function mappedState () {
      var state = this.$store.state; // 將自己 vuex 的 store 的 state 和 getters 保存
      var getters = this.$store.getters;
      // 先不看 namespace 部分
      // ......
      // 這個函數裏面會判斷真正的值 ref.val 是函數仍是普通值
      return typeof val === 'function'
        ? val.call(this, state, getters) // 函數會被直接執行,而且傳入 vuex 的state 和 getters
        : state[val] // 值會直接放到  vuex 的state 裏面
    };
  });
  return res
});
  • states參數在這裏只有2種,一種是對象{},一種是數組[]。由於normalizeMap只作了這2個判斷。
  • states 會先經過normalizeMap進行序列化,轉換爲一個數組,數組元素是{ key, val: key } 這種結構的。
  • 這裏須要注意一下:若是傳入的是對象裏面只有函數,以下例的countPlusLocalState,那麼被 object.keys獲取的 key 是函數的名字。
// 例如傳入的state 是一個數組,以下:
[
   {
     count: state => state.count,   
   }
]
// 那麼被normalizeMap轉換後:
// 即轉換爲{ key, val: key })
[
    { 
     key, // key 是對象{count: state => state.count}
     val: key // val是對象{count: state => state.count}
    },
    //.....
]

//------------------------

// 例如傳入的state 是一個對象,以下:
{
  count: state => state.count,
}
// 那麼被normalizeMap轉換後:
// 即被Object.keys(map).map(key => ({ key, val: map[key] }))處理後
[
    { 
     key, // key 是count,由於被Object.keys提取出來
     val: map[key] //  val 是state => state.count,這裏以 key 爲鍵找對象的值
    },
    //.....
]
  • normalizeMap(states)格式化以後會使用 forEach 遍歷這個數組:

    • 若是 val 是一個函數,則直接調用這個 val 函數,把當前 store 上的 state 和 getters 做爲參數,返回值做爲 mappedState 的返回值。
    • 不然直接把 this.$store.state[val] 做爲 mappedState 的返回值。
// 被 foreach 遍歷,繼續用上面的例子的state來舉例,由於不是函數,因此被直接返回:
res["count"] = this.$store.state['state => state.count']
// 雖然這裏是以數組的寫法,可是在 js 裏面數組的寫法也能夠用在對象上。

//若是是函數的話,會被執行val.call,而且傳入了幾個參數(this, this.$store.state, this.$store.getters)
res["countPlusLocalState"] = this.$store.state['state => state.count']
  • 最終可以造成一個新的數組對象結構,並返回。
  • 由於最終生成的數據就是 computed 的數據格式,因此直接將這個對象傳給 computed 就能夠直接使用了。

這是mapState通過源代碼解析前和後的對比:

// states 爲對象時候,mapState轉換前
computed: mapState({
    count: state => state.count,
    // 傳字符串參數 'count' 等同於 `state => state.count`
    countAlias: 'count',
    // 爲了可以使用 `this` 獲取局部狀態,必須使用常規函數
    countPlusLocalState (state) {
          return state.count + this.localCount
    }
})

// states 爲對象時候,mapState轉換後
computed: { 
    count() { 
       // 直接轉換爲通常的計算屬性的使用方式
        return this.$store.state.count 
    },
    countAlias() { 
        // 也是轉爲通常的計算屬性的使用方式,只不過有指定名字的會使用中括號括起來
        return this.$store.state['count']
    }, 
    countPlusLocalState() { 
        // 由於是函數,因此會被直接執行,而且傳入了當前 store 上的 state 和 getters做爲參數
        //(但這裏沒使用 getters)
        return this.$store.state.count + this.localCount 
    } 
}

組件仍然保有局部狀態

使用 Vuex 並不意味着你須要將全部的狀態放入 Vuex。雖然將全部的狀態放到 Vuex 會使狀態變化更顯式和易調試,但也會使代碼變得冗長和不直觀。若是有些狀態嚴格屬於單個組件,最好仍是做爲組件的局部狀態。你應該根據你的應用開發須要進行權衡和肯定。

關於 getter

Vuex 容許咱們在 store 中定義「getter」(能夠認爲是 store 的計算屬性),就像計算屬性同樣,getter 的返回值會根據它的依賴被緩存起來,且只有當它的依賴值發生了改變纔會被從新計算。

// 初始化 getter
const store = new Vuex.Store({
  state: {
    todos: [
      { id: 1, text: '...', done: true },
      { id: 2, text: '...', done: false }
    ]
  },
  getters: { // 這就是 getters
    doneTodos: state => {
      return state.todos.filter(todo => todo.done)
    }
  }
})

// 使用getter
store.getters.doneTodos // -> [{ id: 1, text: '...', done: true }]
// 或者能夠this.$store.getters.xxxx 這樣使用。
// 能夠在第二個參數裏面傳一個 getter 做爲參數
getters: {
  // ...
  doneTodosCount: (state, getters) => {
      // 傳入了以前設置的doneTodos的 getters,因此直接使用了doneTodos
    return getters.doneTodos.length
  }
}

store.getters.doneTodosCount // -> 1

// 讓 getter 返回一個函數,來實現給 getter 傳參。在你對 store 裏的數組進行查詢時很是有用。
getters: {
  // ...
  getTodoById: (state) => (id) => { // 返回一個函數
    return state.todos.find(todo => todo.id === id)
  }
}

// 對返回的函數傳入參數來使用
store.getters.getTodoById(2) // -> { id: 2, text: '...', done: false }

mapGetters 輔助函數

mapGetters 輔助函數僅僅是將 store 中的 getter 映射到局部計算屬性。

參考 vuex 源代碼,相似的處理也是跟...mapState相似:

export function mapGetters(getters) {
    const res = {}
    // 先格式化,而後再處理
    normalizeMap(getters).forEach(({key, val}) => {
        res[key] = function mappedGetter() {
            if (!(val in this.$store.getters)) {
                console.error(`[vuex] unknown getter: ${val}`)
            }
            return this.$store.getters[val]
        }
    })
    return res
}
相比 mapState 來講,他簡單一點。

惟一的區別就是它的 val 不能是函數,只能是一個字符串,並且會檢查 val in this.$store.getters 的值,若是爲 false 會輸出一條錯誤日誌。

關於三個點...mapstate 處理

其實三個點就是es6的擴展運算符。

能夠將一個數組轉爲用逗號分隔的參數序列,也能夠將對象進行展開,若是應用到 mapstate 身上就是:

須要注意:vue的 computed 是對象,裏面的屬性是對象屬性。
computed: {
  // 通常 computed 對象屬性
  now: function () {
    return Date.now()
  }
  // 使用對象展開運算符將此對象混入到外部對象中
  ...mapGetters([
      'doneTodosCount',
      'anotherGetter',
      // ...
    ])

  // 轉換後是這樣,跟通常 computed 對象屬性差很少
  doneTodosCount:function(){},
  anotherGetter:function(){}
}

參考:

相關文章
相關標籤/搜索