vuex 版本為
^2.3.1
,按照我自己的理解來整理vuex。
關於 state
每個vuex 應用只有一個 store 例項,所以使用起來不會太複雜,對於定位錯誤狀態和操作會很方便。
簡單用法:在vuex 的計算屬性中返回vuex 的狀態
最基本的使用方式,通過在vue檔案裡面初始化 vuex 的 store 來進行操作 vuex 的資料:如下例子:
// 在元件裡面使用 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)
):
// 初始化 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 的實現:
// 這是子元件 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 來實現資料儲存。
我整理了一下完整的例子,這是 jsrun的例子:
jsrun.net/qWqKp
高階用法:mapState 輔助函式
當一個元件需要獲取多個狀態時候,將這些狀態都宣告為計算屬性會有些重複和冗餘。為了解決這個問題,我們可以使用 mapState 輔助函式幫助我們生成計算屬性,讓你少按幾次鍵。(其實就是自動轉換了一些語法輸出)
import { mapState } from `vuex`
需要先引入才可以使用
mapState使用前後對比:
// 不使用mapState時:
computed: {
count () {
return this.$store.state.count
}
}
// 使用mapState時:
computed: mapState({
count: state => state.count,
})
複製程式碼
如果在大批量的類似這種的計算屬性的話,使用 mapState 會更加便捷,而且不只是普通字串,函式也可以轉換,確實是比較方便的。
這裡會有一個疑問,mapState到底做了什麼事情,怎麼將程式碼轉為 vue 能識別的程式碼?所以需要參考 vuex 原始碼中關於mapState的部分:(我找了當前 vuex 版本3.01的原始碼:github.com/vuejs/vuex/…)
這是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(){}
}
複製程式碼
參考:
- Vuex 2.0 原始碼分析(下)_慕課手記
- 這篇文章描述更為清楚,可以先看這篇:segmentfault.com/a/119000001…