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)用開始:
newVue({// statedata () {return{count:0}? ? ? },// viewtemplate: `
1
2
3
4
5
6
7
8
9
10
11
12
13
14
1
2
3
4
5
6
7
8
9
10
11
12
13
14
這個狀態(tài)自管理應(yīng)用包含以下幾個部分:
state茂嗓,驅(qū)動應(yīng)用的數(shù)據(jù)源餐茵; view,以聲明方式將state映射到視圖述吸; actions忿族,響應(yīng)在view上的用戶輸入導(dǎo)致的狀態(tài)變化。
我的理解是 state狀態(tài)就是數(shù)據(jù)源蝌矛,通常用data道批,view是視圖層不用說,通常用template朴读,action就是方法進行一些數(shù)據(jù)操作屹徘,通常用methods。
當(dāng)我們的應(yīng)用遇到多個組件共享狀態(tài)時衅金,單向數(shù)據(jù)流的簡潔性很容易被破壞:
-多個視圖依賴于同一狀態(tài)噪伊。
-來自不同視圖的行為需要變更同一狀態(tài)。
應(yīng)該是 很多視圖層文件都是同一個數(shù)據(jù)來源氮唯,不同視圖的操作需要改為同一個數(shù)據(jù)源鉴吹。
對于問題一,傳參的方法對于多層嵌套的組件將會非常繁瑣惩琉,并且對于兄弟組件間的狀態(tài)傳遞無能為力豆励。對于問題二,我們經(jīng)常會采用父子組件直接引用或者通過事件來變更和同步狀態(tài)的多份拷貝。以上的這些模式非常脆弱良蒸,通常會導(dǎo)致無法維護的代碼技扼。
因此,我們?yōu)槭裁床话呀M件的共享狀態(tài)抽取出來嫩痰,以一個全局單例模式管理呢剿吻?在這種模式下,我們的組件樹構(gòu)成了一個巨大的“視圖”串纺,不管在樹的哪個位置丽旅,任何組件都能獲取狀態(tài)或者出發(fā)行為!
另外纺棺,通過定義和隔離狀態(tài)管理中的各種概念并強制遵守一定的規(guī)則榄笙,我們的代碼將會變得更結(jié)構(gòu)化且易維護。
這就是 Vuex 背后的基本思想祷蝌,借鑒了 Flux茅撞、Redux、和 The Elm Architecture杆逗。與其他模式不同的是乡翅,Vuex
是專門為 Vue.js 設(shè)計的狀態(tài)管理庫,以利用 Vue.js 的細粒度數(shù)據(jù)響應(yīng)機制來進行高效的狀態(tài)更新罪郊。
也就是遇到上述問題怎么辦呢蠕蚜,Vuex將數(shù)據(jù)源打包了,然后要操作的時候悔橄,都從這個數(shù)據(jù)源來操作靶累,不會有那種繼承父組件,多層組件嵌套導(dǎo)致不利于維護的情況癣疟。
什么情況下我應(yīng)該使用 Vuex挣柬?
雖然 Vuex 可以幫助我們管理共享狀態(tài),但也附帶了更多的概念和框架睛挚。這需要對短期和長期效益進行權(quán)衡邪蛔。
如果您不打算開發(fā)大型單頁應(yīng)用,使用 Vuex 可能是繁瑣冗余的扎狱。確實是如此——如果您的應(yīng)用夠簡單侧到,您最好不要使用 Vuex。一個簡單的
global event bus
就足夠您所需了淤击。但是匠抗,如果您需要構(gòu)建是一個中大型單頁應(yīng)用,您很可能會考慮如何更好地在組件外部管理狀態(tài)污抬,Vuex 將會成為自然而然的選擇汞贸。引用
Redux 的作者 Dan Abramov 的話說就是:
Flux架構(gòu)就像眼鏡:您自會知道什么時候需要它。
其實我真的覺得這段話說了等于沒說。矢腻。门驾。 ㄟ( ▔, ▔ )ㄏ
每一個 Vuex 應(yīng)用的核心就是 store(倉庫)√けぃ”store” 基本上就是一個容器猎唁,它包含著你的應(yīng)用中大部分的狀態(tài)(即state)。Vuex 和單純的全局對象有以下兩點不同:
1.Vuex 的狀態(tài)存儲是響應(yīng)式的顷蟆。當(dāng) Vue 組件從 store 中讀取狀態(tài)的時候,若 store中的狀態(tài)發(fā)生變化腐魂,那么相應(yīng)的組件也會相應(yīng)地得到高效更新帐偎。
2.你不能直接改變 store 中的狀態(tài)。改變 store 中的狀態(tài)的唯一途徑就是顯式地提交mutations蛔屹。這樣使得我們可以方便地跟蹤每一個狀態(tài)的變化削樊,從而讓我們能夠?qū)崿F(xiàn)一些工具幫助我們更好地了解我們的應(yīng)用。
1大概也就是數(shù)據(jù)綁定兔毒,同步更新數(shù)據(jù)漫贞,2不是很懂,mutations是什么東西育叁?
最簡單的 Store
提示:我們將在后續(xù)的文檔示例代碼中使用 ES2015 語法迅脐。如果你還沒能掌握 ES2015,你得抓緊了豪嗽!
安裝 Vuex 之后谴蔑,讓我們來創(chuàng)建一個 store。創(chuàng)建過程直截了當(dāng)——僅需要提供一個初始 state 對象和一些 mutations:
// 如果在模塊化構(gòu)建系統(tǒng)中龟梦,請確保在開頭調(diào)用了 Vue.use(Vuex)const store =newVuex.Store({? state: {count:0},? mutations: {? ? increment (state) {? ? ? state.count++? ? }? }})
1
2
3
4
5
6
7
8
9
10
11
12
1
2
3
4
5
6
7
8
9
10
11
12
現(xiàn)在隐锭,你可以通過 store.state 來獲取狀態(tài)對象,以及通過 store.commit 方法觸發(fā)狀態(tài)變更:
store.commit('increment')console.log(store.state.count)// -> 1
1
2
3
1
2
3
再次強調(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 中提交 mutations完丽。
這是一個最基本的 Vuex 記數(shù)應(yīng)用示例恋技。
接下來,我們將會更深入地探討一些核心概念逻族。讓我們先從 State 概念開始蜻底。
反正每次對數(shù)據(jù)對象進行操作的時候,都需要進行commit聘鳞,好處就是他上面說的薄辅,但是現(xiàn)在不在項目中,肯定體會不到好處抠璃,就像redux作者說的“您自會知道什么時候需要它站楚。”
單一狀態(tài)樹
Vuex 使用 單一狀態(tài)樹 ——
是的搏嗡,用一個對象就包含了全部的應(yīng)用層級狀態(tài)窿春。至此它便作為一個『唯一數(shù)據(jù)源(SSOT)』而存在。這也意味著采盒,每個應(yīng)用將僅僅包含一個 store
實例旧乞。單一狀態(tài)樹讓我們能夠直接地定位任一特定的狀態(tài)片段,在調(diào)試的過程中也能輕易地取得整個當(dāng)前應(yīng)用狀態(tài)的快照磅氨。
單狀態(tài)樹和模塊化并不沖突 —— 在后面的章節(jié)里我們會討論如何將狀態(tài)和狀態(tài)變更事件分布到各個子模塊中尺栖。
每個應(yīng)用將僅僅包含一個 store
在 Vue 組件中獲得 Vuex 狀態(tài)
那么我們?nèi)绾卧?Vue 組件中展示狀態(tài)呢?由于 Vuex 的狀態(tài)存儲是響應(yīng)式的悍赢,從 store
實例中讀取狀態(tài)最簡單的方法就是在計算屬性中返回某個狀態(tài):
// 創(chuàng)建一個 Counter 組件const Counter = {? template: `
1
2
3
4
5
6
7
8
9
1
2
3
4
5
6
7
8
9
每當(dāng) 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)):
constapp =newVue({? el:'#app',// 把 store 對象提供給 “store” 選項锌杀,這可以把 store 的實例注入所有的子組件store,? components: { Counter },? template: `? ? `})
1
2
3
4
5
6
7
8
9
10
11
1
2
3
4
5
6
7
8
9
10
11
通過在根實例中注冊 store 選項甩栈,該 store 實例會注入到根組件下的所有子組件中,且子組件能通過 this.$store訪問到糕再。讓我們更新下 Counter 的實現(xiàn):
const Counter = {? template: `
1
2
3
4
5
6
7
8
1
2
3
4
5
6
7
8
通過一種類似虛擬主機的機制量没,將store注入到每個子組件,然后子組件就能操作store了突想,還是有點像繼承殴蹄。
mapState 輔助函數(shù)
當(dāng)一個組件需要獲取多個狀態(tài)時候究抓,將這些狀態(tài)都聲明為計算屬性會有些重復(fù)和冗余。為了解決這個問題袭灯,我們可以使用 mapState
輔助函數(shù)幫助我們生成計算屬性
這個我看的不是很懂刺下,只知道他是輔助計算屬性的。
mapState函數(shù)返回的是一個對象稽荧。我們?nèi)绾螌⑺c局部計算屬性混合使用呢橘茉?通常,我們需要使用一個工具函數(shù)將多個對象合并為一個姨丈,以使我們可以將最終對象傳給 computed 屬性畅卓。但是自從有了對象展開運算符(現(xiàn)處于 ECMASCript 提案 stage-3 階段),我們可以極大地簡化寫法构挤。
上面的沒懂髓介,那這個等等再說。
有時候我們需要從 store 中的 state 中派生出一些狀態(tài)筋现,例如對列表進行過濾并計數(shù):
computed:{? doneTodosCount () {? ? return this.$store.state.todos.filter(todo => todo.done).length}}
1
2
3
4
5
1
2
3
4
5
如果有多個組件需要用到此屬性,我們要么復(fù)制這個函數(shù)箱歧,或者抽取到一個共享函數(shù)然后在多處導(dǎo)入它 —— 無論哪種方式都不是很理想矾飞。
Vuex 允許我們在 store 中定義『getters』(可以認為是 store 的計算屬性)。Getters 接受 state 作為其第一個參數(shù):
conststore =newVuex.Store({? state: {? ? todos: [? ? ? { id:1, text:'...', done:true},? ? ? { id:2, text:'...', done:false}? ? ]? },? getters: {? ? doneTodos: state => {returnstate.todos.filter(todo => todo.done)? ? }? }})
1
2
3
4
5
6
7
8
9
10
11
12
13
1
2
3
4
5
6
7
8
9
10
11
12
13
Getters 會暴露為 store.getters 對象:
store.getters.doneTodos // -> [{ id:1, text:'...',done:true}]
1
1
Getters 也可以接受其他 getters 作為第二個參數(shù):
getters: {? //...doneTodosCount: (state, getters) => {returngetters.doneTodos.length? }}store.getters.doneTodosCount // ->1
1
2
3
4
5
6
7
1
2
3
4
5
6
7
我們可以很容易地在任何組件中使用它:
computed:{? doneTodosCount () {? ? return this.$store.getters.doneTodosCount}}
1
2
3
4
5
1
2
3
4
5
getters可以認為是 store 的計算屬性呀邢,也就是多次調(diào)用洒沦,所以將其封裝成getters,方便調(diào)用价淌。
mapGetters 輔助函數(shù)
mapGetters 輔助函數(shù)僅僅是將 store 中的 getters 映射到局部計算屬性:
import { mapGetters } from'vuex'export default {? //...computed: {? // 使用對象展開運算符將 getters 混入 computed 對象中? ? ...mapGetters(['doneTodosCount','anotherGetter',? ? ? //...])? }}
1
2
3
4
5
6
7
8
9
10
11
12
13
1
2
3
4
5
6
7
8
9
10
11
12
13
如果你想將一個 getter 屬性另取一個名字申眼,使用對象形式:
mapGetters({? // 映射 this.doneCount 為 store.getters.doneTodosCountdoneCount:'doneTodosCount'})
1
2
3
4
1
2
3
4
和上面的state一樣,沒太明白蝉衣。不著急括尸。
更改 Vuex 的 store 中的狀態(tài)的唯一方法是提交 mutation。Vuex 中的 mutations 非常類似于事件:每個 mutation 都有一個字符串的 事件類型 (type) 和 一個 回調(diào)函數(shù) (handler)病毡。這個回調(diào)函數(shù)就是我們實際進行狀態(tài)更改的地方濒翻,并且它會接受 state 作為第一個參數(shù):
const store =newVuex.Store({? state: {count:1},? mutations: {? ? increment (state) {// 變更狀態(tài)state.count++? ? }? }})
1
2
3
4
5
6
7
8
9
10
11
1
2
3
4
5
6
7
8
9
10
11
你不能直接調(diào)用一個 mutation handler。這個選項更像是事件注冊:“當(dāng)觸發(fā)一個類型為 increment 的 mutation 時啦膜,調(diào)用此函數(shù)有送。”要喚醒一個 mutation handler僧家,你需要以相應(yīng)的 type 調(diào)用 store.commit 方法:
store.commit('increment')
1
1
我到現(xiàn)在還是沒有明白 mutations是干什么的雀摘,具體有什么用。只知道要操作state就是要commit mutations八拱。
提交載荷(Payload)
你可以向 store.commit 傳入額外的參數(shù)阵赠,即 mutation 的 載荷(payload):
//...mutations: {? increment (state, n) {? ? state.count += n? }}store.commit('increment',10)
1
2
3
4
5
6
7
1
2
3
4
5
6
7
在大多數(shù)情況下涯塔,載荷應(yīng)該是一個對象,這樣可以包含多個字段并且記錄的 mutation 會更易讀:
//...mutations: {? increment (state, payload) {? ? state.count += payload.amount? }}store.commit('increment', {? amount:10})
1
2
3
4
5
6
7
8
9
1
2
3
4
5
6
7
8
9
不知道載荷用的多不多豌注,感覺還挺多的伤塌,荷載就是 commit額外的參數(shù)。
對象風(fēng)格的提交方式
提交 mutation 的另一種方式是直接使用包含 type 屬性的對象:
store.commit({type:'increment',? amount:10})
1
2
3
4
1
2
3
4
當(dāng)使用對象風(fēng)格的提交方式轧铁,整個對象都作為載荷傳給 mutation 函數(shù)每聪,因此 handler 保持不變:
mutations:{? increment (state, payload) {? ? state.count+= payload.amount}}
1
2
3
4
5
1
2
3
4
5
感覺這種風(fēng)格更適用于vue,更像vue的寫法
Mutations 需遵守 Vue 的響應(yīng)規(guī)則
既然 Vuex 的 store 中的狀態(tài)是響應(yīng)式的齿风,那么當(dāng)我們變更狀態(tài)時药薯,監(jiān)視狀態(tài)的 Vue 組件也會自動更新。這也意味著 Vuex 中的 mutation 也需要與使用 Vue 一樣遵守一些注意事項:
最好提前在你的 store 中初始化好所有所需屬性救斑。
當(dāng)需要在對象上添加新屬性時童本,你應(yīng)該使用Vue.set(obj, 'newProp', 123),或者 -
以新對象替換老對象。例如脸候,利用 stage-3 的對象展開運算符我們可以這樣寫:
state.obj= { ...state.obj, newProp:123}
1
1
規(guī)則很重要穷娱,我愣是擠出了這幾個沒用的字。 Σ( ° △ °|||)︴
使用常量替代 Mutation 事件類型
使用常量替代 mutation 事件類型在各種 Flux 實現(xiàn)中是很常見的模式运沦。這樣可以使 linter 之類的工具發(fā)揮作用泵额,同時把這些常量放在單獨的文件中可以讓你的代碼合作者對整個 app 包含的 mutation 一目了然:
// mutation-types.jsexport const SOME_MUTATION ='SOME_MUTATION'// store.jsimport Vuex from'vuex'import { SOME_MUTATION } from'./mutation-types'const store = new Vuex.Store({? state: {...},? mutations: {? ? // 我們可以使用 ES2015 風(fēng)格的計算屬性命名功能來使用一個常量作為函數(shù)名? ? [SOME_MUTATION] (state) {? ? ? // mutate state? ? }? }})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
用不用常量取決于你 —— 在需要多人協(xié)作的大型項目中,這會很有幫助携添。但如果你不喜歡嫁盲,你完全可以不這樣做。
什么可不可以這樣做烈掠,這個到底是干嘛的啊 ヾ(?`Д′?)羞秤,難道是方便改事件名?
mutation 必須是同步函數(shù)
一條重要的原則就是要記住 mutation 必須是同步函數(shù)左敌。為什么瘾蛋?請參考下面的例子:
mutations:{? someMutation (state) {? ? api.callAsyncMethod(() => {? ? ? state.count++? ? })? }}
1
2
3
4
5
6
7
1
2
3
4
5
6
7
現(xiàn)在想象,我們正在 debug 一個 app 并且觀察 devtool 中的 mutation 日志母谎。每一條 mutation 被記錄瘦黑,devtools 都需要捕捉到前一狀態(tài)和后一狀態(tài)的快照。然而奇唤,在上面的例子中 mutation 中的異步函數(shù)中的回調(diào)讓這不可能完成:因為當(dāng) mutation 觸發(fā)的時候幸斥,回調(diào)函數(shù)還沒有被調(diào)用,devtools 不知道什么時候回調(diào)函數(shù)實際上被調(diào)用 —— 實質(zhì)上任何在回調(diào)函數(shù)中進行的的狀態(tài)的改變都是不可追蹤的咬扇。
這個如果不是同步函數(shù)的話甲葬,就顯示不了回調(diào)函數(shù)的狀態(tài)日志了,可以這樣理解嗎懈贺?
在組件中提交 Mutations
你可以在組件中使用this.$store.commit('xxx')提交 mutation经窖,或者使用 mapMutations 輔助函數(shù)將組件中的 methods 映射為store.commit調(diào)用(需要在根節(jié)點注入 store)坡垫。
import { mapMutations } from'vuex'export default {? //...methods: {? ? ...mapMutations(['increment'// 映射 this.increment() 為 this.$store.commit('increment')? ? ]),? ? ...mapMutations({? ? ? add:'increment'// 映射 this.add() 為 this.$store.commit('increment')? ? })? }}
1
2
3
4
5
6
7
8
9
10
11
12
13
1
2
3
4
5
6
7
8
9
10
11
12
13
這個倒是能看懂
下一步:Actions
在 mutation 中混合異步調(diào)用會導(dǎo)致你的程序很難調(diào)試。例如画侣,當(dāng)你能調(diào)用了兩個包含異步回調(diào)的 mutation 來改變狀態(tài)冰悠,你怎么知道什么時候回調(diào)和哪個先回調(diào)呢?這就是為什么我們要區(qū)分這兩個概念配乱。在 Vuex 中溉卓,mutation 都是同步事務(wù):
store.commit('increment')// 任何由"increment"導(dǎo)致的狀態(tài)變更都應(yīng)該在此刻完成。