- State
- Getter
State
#單一狀態樹Satte
Vuex 使用單一狀態樹——是的,用一個對象就包含了全部的應用層級狀態。至此它便作爲一個“唯一數據源 (SSOT)”而存在。這也意味着,每個應用將僅僅包含一個 store 實例。單一狀態樹讓我們能夠直接地定位任一特定的狀態片段,在調試的過程中也能輕易地取得整個當前應用狀態的快照。
單狀態樹和模塊化並不衝突——在後面的章節裏我們會討論如何將狀態和狀態變更事件分佈到各個子模塊中。
#在 Vue 組件中獲得 Vuex 狀態
那麼我們如何在 Vue 組件中展示狀態呢?由於 Vuex 的狀態存儲是響應式的,從 store 實例中讀取狀態最簡單的方法就是在計算屬性中返回某個狀態:
// 創建一個 Counter 組件
const Counter = {
template: `<div>{{ count }}</div>`,
computed: {
count () {
return store.state.count
}
}
}
每當 store.state.count
變化的時候, 都會重新求取計算屬性,並且觸發更新相關聯的 DOM。
然而,這種模式導致組件依賴全局狀態單例。在模塊化的構建系統中,在每個需要使用 state 的組件中需要頻繁地導入,並且在測試組件時需要模擬狀態。
Vuex 通過 store
選項,提供了一種機制將狀態從根組件“注入”到每一個子組件中(需調用 Vue.use(Vuex)
):
const app = new Vue({
el: '#app',
// 把 store 對象提供給 “store” 選項,這可以把 store 的實例注入所有的子組件
store,
components: { Counter },
template: `
<div class="app">
<counter></counter>
</div>
`
})
通過在根實例中註冊 store
選項,該 store 實例會注入到根組件下的所有子組件中,且子組件能通過 this.$store
訪問到。讓我們更新下 Counter
的實現:
const Counter = {
template: `<div>{{ count }}</div>`,
computed: {
count () {
return this.$store.state.count
}
}
}
#mapState
輔助函數
當一個組件需要獲取多個狀態時候,將這些狀態都聲明爲計算屬性會有些重複和冗餘。爲了解決這個問題,我們可以使用 mapState
輔助函數幫助我們生成計算屬性,讓你少按幾次鍵:
// 在單獨構建的版本中輔助函數爲 Vuex.mapState
import { mapState } from 'vuex'
export default {
// ...
computed: mapState({
// 箭頭函數可使代碼更簡練
count: state => state.count,
// 傳字符串參數 'count' 等同於 `state => state.count`
countAlias: 'count',
// 爲了能夠使用 `this` 獲取局部狀態,必須使用常規函數
countPlusLocalState (state) {
return state.count + this.localCount
}
})
}
當映射的計算屬性的名稱與 state 的子節點名稱相同時,我們也可以給 mapState
傳一個字符串數組。
computed: mapState([
// 映射 this.count 爲 store.state.count
'count'
])
#對象展開運算符
mapState
函數返回的是一個對象。我們如何將它與局部計算屬性混合使用呢?通常,我們需要使用一個工具函數將多個對象合併爲一個,以使我們可以將最終對象傳給 computed
屬性。但是自從有了對象展開運算符(現處於 ECMASCript 提案 stage-4 階段),我們可以極大地簡化寫法:
computed: {
localComputed () { /* ... */ },
// 使用對象展開運算符將此對象混入到外部對象中
...mapState({
// ...
})
}
#組件仍然保有局部狀態
使用 Vuex 並不意味着你需要將所有的狀態放入 Vuex。雖然將所有的狀態放到 Vuex 會使狀態變化更顯式和易調試,但也會使代碼變得冗長和不直觀。如果有些狀態嚴格屬於單個組件,最好還是作爲組件的局部狀態。你應該根據你的應用開發需要進行權衡和確定。
Getter
有時候我們需要從 store 中的 state 中派生出一些狀態,例如對列表進行過濾並計數:
computed: {
doneTodosCount () {
return this.$store.state.todos.filter(todo => todo.done).length
}
}
如果有多個組件需要用到此屬性,我們要麼複製這個函數,或者抽取到一個共享函數然後在多處導入它——無論哪種方式都不是很理想。
Vuex 允許我們在 store 中定義“getter”(可以認爲是 store 的計算屬性)。就像計算屬性一樣,getter 的返回值會根據它的依賴被緩存起來,且只有當它的依賴值發生了改變纔會被重新計算。
Getter 接受 state 作爲其第一個參數:
const store = new Vuex.Store({
state: {
todos: [
{ id: 1, text: '...', done: true },
{ id: 2, text: '...', done: false }
]
},
getters: {
doneTodos: state => {
return state.todos.filter(todo => todo.done)
}
}
})
#通過屬性訪問
Getter 會暴露爲 store.getters
對象,你可以以屬性的形式訪問這些值:
store.getters.doneTodos // -> [{ id: 1, text: '...', done: true }]
Getter 也可以接受其他 getter 作爲第二個參數:
getters: {
// ...
doneTodosCount: (state, getters) => {
return getters.doneTodos.length
}
}
store.getters.doneTodosCount // -> 1
我們可以很容易地在任何組件中使用它:
computed: {
doneTodosCount () {
return this.$store.getters.doneTodosCount
}
}
注意,getter 在通過屬性訪問時是作爲 Vue 的響應式系統的一部分緩存其中的。
#通過方法訪問
你也可以通過讓 getter 返回一個函數,來實現給 getter 傳參。在你對 store 裏的數組進行查詢時非常有用。
getters: {
// ...
getTodoById: (state) => (id) => {
return state.todos.find(todo => todo.id === id)
}
}
store.getters.getTodoById(2) // -> { id: 2, text: '...', done: false }
注意,getter 在通過方法訪問時,每次都會去進行調用,而不會緩存結果。
#mapGetters
輔助函數
mapGetters
輔助函數僅僅是將 store 中的 getter 映射到局部計算屬性:
import { mapGetters } from 'vuex'
export default {
// ...
computed: {
// 使用對象展開運算符將 getter 混入 computed 對象中
...mapGetters([
'doneTodosCount',
'anotherGetter',
// ...
])
}
}
如果你想將一個 getter 屬性另取一個名字,使用對象形式:
mapGetters({
// 把 `this.doneCount` 映射爲 `this.$store.getters.doneTodosCount`
doneCount: 'doneTodosCount'
})