Vuex是什么?
Vuex
是一個專為 Vue.js
應(yīng)用程序開發(fā)的狀態(tài)管理模式坎缭。它采用集中式存儲管理應(yīng)用的所有組件的狀態(tài)掏呼,并以相應(yīng)的規(guī)則保證狀態(tài)以一種可預(yù)測的方式發(fā)生變化。Vuex
也集成到 Vue
的官方調(diào)試工具 devtools extension
憎夷,提供了諸如零配置的 time-travel
調(diào)試、狀態(tài)快照導(dǎo)入導(dǎo)出等高級調(diào)試功能祥得。
什么是“狀態(tài)管理模式”蒋得?
讓我們從一個簡單的 Vue 計數(shù)應(yīng)用開始:
new Vue({
// state
data () {
return {
count: 0
}
},
// view
template: `
<div>{{ count }}</div>
`,
// actions
methods: {
increment () {
this.count++
}
}
})
這個狀態(tài)自管理應(yīng)用包含以下幾個部分:
state,驅(qū)動應(yīng)用的數(shù)據(jù)源饮焦;
view,以聲明方式將 state 映射到視圖县踢;
actions,響應(yīng)在 view 上的用戶輸入導(dǎo)致的狀態(tài)變化暇咆。
但是丙曙,當我們的應(yīng)用遇到多個組件共享狀態(tài)時,單向數(shù)據(jù)流的簡潔性很容易被破壞:
1.多個視圖依賴于同一個狀態(tài)扯旷。
2.來自不同視圖的行為需要變更同一個狀態(tài)索抓。
對于問題一:傳參的方式對于多層嵌套的組件將會非常的繁瑣,并且對于兄弟組件之間的狀態(tài)傳遞無能為力逼肯,對于問題二,我們經(jīng)常會采用父子組件直接引用或者通過事件來變更和同步狀態(tài)的多份拷貝大刊。以上的這些模式非常脆弱三椿,通常會導(dǎo)致無法維護的代碼。
因此 我們?yōu)槭裁床话呀M件的共享狀態(tài)抽取出來伴郁,以一個全局單例模式進行管理呢蛋叼?
在這種模式下,我們的組件樹構(gòu)成了一個巨大的“視圖” 不管在樹的哪一個位置鸦列,任何組件都能夠獲取狀態(tài)或者觸發(fā)行為。
每一個Vuex
應(yīng)用的核心就是store
(倉庫)store
基本上就是一個容器,它包含著應(yīng)用中大部分的狀態(tài)骆姐,Vuex
和單純的全局對象有以下兩點不同:
1.`Vuex`的狀態(tài)存儲是響應(yīng)式的,當`Vuex`組件從`store`中狀態(tài)的時候玻褪,若`store`中的中的狀態(tài)發(fā)生變化,那么相應(yīng)的組件也會相應(yīng)的得到高效的更新同规。
2.你不能直接更改`store` 里面的數(shù)據(jù)窟社,改變`store` 中的狀態(tài)的唯一途徑就是顯示的
提交(commit)mutation 這樣使得我們可以方便地跟蹤每一個狀態(tài)的變化,從而讓我們能夠?qū)崿F(xiàn)一些工具幫助我們更好地了解我們的應(yīng)用关炼。
最簡單的store:
// 如果在模塊化構(gòu)建系統(tǒng)中匣吊,請確保在開頭調(diào)用了 Vue.use(`Vuex`);
const store = new Vuex.store({
state:{
count:0
},
mutations:{
increment(state){
state.count++
}
}
})
現(xiàn)在,你可以通過 store.state
來獲取狀態(tài)對象社痛,以及通過store.commit
方法觸發(fā)
狀態(tài)變更:
store.commit('increment')
console.log(store.state.count); // 1
再次強調(diào) 我們通過提交mutation的方式命雀,而非直接改變 store.state.count
是因為我們想要更加明確地追蹤到狀態(tài)的變化。這個簡單的約束能讓你的意圖更加明確凡怎,
這樣你在閱讀代碼的時候更容易地解讀應(yīng)用內(nèi)部的狀態(tài)改變赊抖,此外,這樣也能讓我們有機會去實現(xiàn)一些記錄每次狀態(tài)的改變氛雪,保存狀態(tài)快照的調(diào)試工具,有了它浴鸿,我們甚至可以實現(xiàn)如時間穿梭般的調(diào)試體驗弦追。。
由于store
中的狀態(tài)是響應(yīng)式的掸哑,在組件中調(diào)用store
的狀態(tài)簡單到僅需要在計算屬性中返回即可,觸發(fā)變化也僅僅是在組件中methods
中提交mutation
苗分。
State:單一狀態(tài)樹
Vuex
使用單一狀態(tài)樹-——是的,用一個對象就包含了全部的應(yīng)用層級狀態(tài)奴饮,至此它便作為一個“唯一數(shù)據(jù)源”而存在择浊,這也意味著,每一個應(yīng)用將僅僅包含一個store
實例叉瘩。
在Vue組件中獲得Vuex
狀態(tài):
那么我們?nèi)绾卧赩ue組件中展示狀態(tài)呢粘捎?由于Vuex
的狀態(tài)存儲是響應(yīng)式的,從store
實例中讀取狀態(tài)的最簡單的方法就是在計算屬性中返回某一個狀態(tài)泳桦。
const Counter = {
template: `<div>{{ count }}</div>`,
computed:{
count(){
return store.state.count
}
}
}
每當store.state.count
變化的時候娩缰,都會重新請求計算屬性,并且觸發(fā)
更新相關(guān)聯(lián)的DOM.
然而拼坎,這種模式導(dǎo)致組件依賴全局狀態(tài)單例泰鸡,在模塊化的構(gòu)建系統(tǒng)中,在每個需要使用的
state 的組件中需要頻繁地導(dǎo)入盛龄,并且在測試組件時候需要模擬狀態(tài)。
Vuex
通過 store
選項 啊鸭,提供了一種機制將狀態(tài)從根組件注入到每一個子組件中(需要調(diào)用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的實現(xiàn):
const Counter = {
template: `<div>{{ count }}</div>`,
computed: {
count () {
return this.$store.state.count
}
}
}
mapState 輔助函數(shù):
當一個組件需要獲取多個狀態(tài)的時候曲楚,將這個狀態(tài)都聲明為計算屬性有些重復(fù)和冗余,為了解決這個問題抚垃,我們可以使用mapState
輔助函數(shù)幫助我們生成計算屬性趟大,
// 在單獨構(gòu)建的版本中輔助函數(shù)為 `Vuex.mapState`
import { mapState } from 'Vuex'
export default {
// ...
computed: mapState({
// 箭頭函數(shù)可使代碼更簡練
count: state => state.count,
// 傳字符串參數(shù) 'count' 等同于 `state => state.count`
countAlias: 'count',
// 為了能夠使用 `this` 獲取局部狀態(tài),必須使用常規(guī)函數(shù)
countPlusLocalState (state) {
return state.count + this.localCount
}
})
}
當映射的計算屬性的名稱與state
的子節(jié)點名稱相同的時候罕伯,我們也可以給mapState
傳一個字符串數(shù)組叽讳。
computed: mapState({
// 映射this.count 為 `store`.state.count;
'count'
})
mapState
函數(shù)返回的是一個對象我們?nèi)绾螌⑺c局部計算屬性混合使用呢? 通常我們需要使用一個工具函數(shù)將多個對象和并為一個,使得我們可以將最終對象傳給computed
屬性 邑狸,但是自從有了對象的展開運算涤妒,我們極大的簡化書寫。
computed: {
localComputed () { /* ... */ },
// 使用對象展開運算符將此對象混入到外部對象中
...mapState({
// ...
})
}
組件仍然保有局部狀態(tài):
使用Vuex
并不意味著你需要將所有的狀態(tài)都放入Vuex
硅堆,雖然將所有的狀態(tài)
放到Vuex
會使得狀態(tài)變化更加顯式和易于調(diào)試贿讹,但是也會使得代碼變得冗長和不直觀,如果有些狀態(tài)嚴格屬于單個組價围详,最好還是作為組件的局部狀態(tài)。