Vuex 是一個(gè)專為 Vue 服務(wù),用于管理頁面數(shù)據(jù)狀態(tài)易稠、提供統(tǒng)一數(shù)據(jù)操作的生態(tài)系統(tǒng)言秸。它專注于 MVC 模式中的 Model 層厅瞎,規(guī)定所有數(shù)據(jù)必需遵循action -> mutation -> state(change)
的流程來進(jìn)行(在不需要異步操作并且交互邏輯比較簡單的時(shí)候,也可以直接通過commit
觸發(fā)mutation
來完成mutation -> state(change)
的操作)硝烂,再結(jié)合 Vue 的數(shù)據(jù)視圖雙向綁定特性來實(shí)現(xiàn)頁面的展示更新箕别。統(tǒng)一的頁面狀態(tài)管理以及操作處理,可以讓復(fù)雜的組件交互變得簡單清晰滞谢,同時(shí)可在調(diào)試模式下進(jìn)行時(shí)光機(jī)般的倒退前進(jìn)操作串稀,查看數(shù)據(jù)改變過程,使 code debug 更加方便狮杨。
由于在前段時(shí)間的畢業(yè)設(shè)計(jì)中使用過 Vuex 來管理整個(gè)后臺系統(tǒng)的頁面狀態(tài)母截,也遇到了很多的坑,加上最近要進(jìn)行一個(gè)關(guān)于 Vuex 的技術(shù)分享橄教,就決定參考美團(tuán)點(diǎn)評技術(shù)團(tuán)隊(duì)的相關(guān)博客清寇,通過完整閱讀源碼來深入學(xué)習(xí)一下 Vuex 的實(shí)現(xiàn)原理。
Vuex 學(xué)習(xí)筆記將通過框架原理颤陶、源碼閱讀颗管、總結(jié)和應(yīng)用三個(gè)章節(jié)來分享這次的學(xué)習(xí)心得。
注:第一章主要是對框架原理的介紹滓走,并且通過一些代碼來簡單地描述在項(xiàng)目中應(yīng)該如何使用 Vuex 來對頁面進(jìn)行狀態(tài)管理垦江,真正遇到的項(xiàng)目可能會比這一章介紹的東西復(fù)雜得多。因此如果覺得我在這一章有什么沒有講清楚或者是有什么錯(cuò)誤,都?xì)g迎提出來比吭,也可以參考 Vuex 官方文檔進(jìn)行基礎(chǔ)概念的學(xué)習(xí)绽族。
1. 為什么要用Vuex ?
Vuex 的使用主要取決于我們的項(xiàng)目衩藤,官網(wǎng)的解釋是:
- 當(dāng)我們構(gòu)建一個(gè)中大型的單頁面應(yīng)用程序時(shí)吧慢,Vuex 可以更好的幫助我們在組件外部統(tǒng)一管理狀態(tài)。
舉個(gè)例子赏表,當(dāng)我們在做一個(gè)基于 Vue 的商城項(xiàng)目時(shí)检诗,頂部都會有一個(gè) header 組件,它包含了我們登錄之后的用戶名瓢剿、購物車商品數(shù)量逢慌,而這個(gè)項(xiàng)目里面的任何一個(gè)頁面都會包含這個(gè) header 組件,因此這個(gè)組件是被很多組件引用的间狂。
為了方便我們在任何一個(gè)地方都能拿到這個(gè)組件里面的狀態(tài)攻泼,我們就可以把這些狀態(tài)放到 Vuex 里。而如果沒有 Vuex 的話鉴象,我們會發(fā)現(xiàn)在組件間傳參的時(shí)候需要不停地通過$emit
進(jìn)行傳參忙菠,這種方式是十分麻煩的。組件比較少還好纺弊,一旦組件較多牛欢,需要一個(gè)狀態(tài)被很多組件所使用的話,我們進(jìn)行一次狀態(tài)改變淆游,就需要無數(shù)個(gè)$emit
來完成全局狀態(tài)的改變氢惋,很痛苦也很難管理狀態(tài)。
Vuex 就可以把這些共用的狀態(tài)提出來稽犁,放到 Vuex 里面焰望,這樣所有組件都可以獲取到這個(gè)狀態(tài),可以想象成但并不等于window
里面的全局變量已亥。
2. 框架運(yùn)行流程
對于 Vuex 框架熊赖,官方提供了一個(gè)相對完整的核心思想圖,這代表著整個(gè) Vuex 框架的運(yùn)行流程虑椎。
如圖所示震鹉,Vuex 為 Vue Components 建立起了一個(gè)完整的生態(tài)圈,包括在實(shí)際項(xiàng)目中對 API 的調(diào)用捆姜。圍繞這個(gè)生態(tài)圈传趾,簡要介紹一下各模塊在核心流程中的主要功能:
- Vue Components:Vue 組件。HTML 頁面上泥技,負(fù)責(zé)接收用戶操作等交互行為浆兰,執(zhí)行 dispatch 方法觸發(fā)對應(yīng) action 進(jìn)行回應(yīng)。
- dispatch:操作行為觸發(fā)方法,是唯一能執(zhí)行 action 的方法簸呈。
- actions:操作行為處理模塊榕订。負(fù)責(zé)處理 Vue Components 接收到的所有交互行為。包含同步/異步操作蜕便,支持多個(gè)同名方法劫恒,按照注冊的順序依次觸發(fā)。向后臺 API 請求的操作就在這個(gè)模塊中進(jìn)行轿腺,包括觸發(fā)其他 action 以及提交 mutation 的操作两嘴。該模塊提供了 Promise 的封裝,以支持 action 的鏈?zhǔn)接|發(fā)族壳。
- commit:狀態(tài)改變提交操作方法溶诞。對 mutation 進(jìn)行提交,是唯一能執(zhí)行 mutation 的方法决侈。
- mutations:狀態(tài)改變操作方法。是 Vuex 修改 state 的唯一推薦方法喧务,其他修改方式在嚴(yán)格模式下將會報(bào)錯(cuò)赖歌。該方法只能進(jìn)行同步操作,且方法名只能全局唯一功茴。操作之中會有一些 hook 暴露出來庐冯,以進(jìn)行 state 的監(jiān)控等。
- state:頁面狀態(tài)管理容器對象坎穿。集中存儲 Vue components 中 data 對象的零散數(shù)據(jù)展父,全局唯一,以進(jìn)行統(tǒng)一的狀態(tài)管理玲昧。頁面顯示所需的數(shù)據(jù)從該對象中進(jìn)行讀取栖茉,利用 Vue 的細(xì)粒度數(shù)據(jù)響應(yīng)機(jī)制來進(jìn)行高效的狀態(tài)更新。
- getters:state 對象讀取方法孵延。圖中沒有單獨(dú)列出該模塊吕漂,應(yīng)該被包含在了 render 中,Vue Components 通過該方法讀取全局 state 對象尘应。
核心流程:Vue 組件接收交互行為惶凝,調(diào)用 dispatch 方法觸發(fā) action 相關(guān)處理,若頁面狀態(tài)需要改變犬钢,則調(diào)用 commit 方法提交 mutation 修改 state苍鲜,通過 getters 獲取到 state 新值,重新渲染 Vue Components玷犹,界面隨之更新混滔。
3. 核心模塊
在官方文檔中,提出了五個(gè)核心模塊:State、Getters遍坟、Mutations拳亿、Actions 和 Modules,而在一個(gè)使用 Vuex 的項(xiàng)目中愿伴,State 和 Mutations 是必需存在的肺魁。
3.1 State
什么是 State ?每一個(gè)組件的 data 中包含的變量都可以稱為 State隔节,它也是整個(gè) Vue 的核心概念鹅经。Vue 使用 State 來管理和操作 DOM,從而改變頁面的 UI怎诫,而不是像過去的那些方式(比如jQuery)一樣直接去操作 DOM瘾晃。關(guān)于 State 有兩個(gè)重要的概念:
- State 是唯一的數(shù)據(jù)源。
- 單一的狀態(tài)樹幻妓。
const Counter = {
templete: `<div>{{ count }}</div>`,
computed: {
count() {
return this.$store.count
}
}
}
這段代碼說明了蹦误,當(dāng)我們把變量count
放到 Vuex 里面以后,所有的組件都可以通過這種方式獲取變量肉津,然后把它渲染到模板里面去强胰。如果你覺得這種方法比較 low(我也覺得),那么繼續(xù)往下看妹沙。
3.2 Getters
Getters 并不是必需品偶洋,但是在一些特定的場景還需要它出馬,在核心思想圖上并沒有標(biāo)出它存在的位置距糖,但是從它的使用方式來說玄窝,我覺得它被包含在了 State 和 Vue Components 之間的 Render 里面了。先看一下定義:
- 通過 Getters 可以派生出一些新的狀態(tài)悍引。
怎么理解恩脂?先來看一段代碼:
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)
}
}
})
我們先實(shí)例化了一個(gè) Vuex,假設(shè)有很多組件都在使用 state 里的 todos 這個(gè)變量趣斤,而剛好有一個(gè)組件它只想獲取數(shù)組里done
屬性的是true
的對象东亦,就可以用 getters 來對返回值進(jìn)行處理』I可能有人會說典阵,那我把它拿到組件里再過濾不就行了嗎?好像是這么個(gè)道理镊逝,但是如果有五個(gè)組件需要獲取done
屬性的是true
的對象壮啊,那我們不就得寫五次的過濾方法了嗎?因此撑蒜,getters 也可以理解成是一個(gè)被提取出來的處理 state 對組件返回值的方法歹啼。
3.3 Mutations
提交 Mutations 是 Vuex 提供的唯一可以改變 State 的方法玄渗。看一下官方的話:
- 更改 Vuex 的 store 中的狀態(tài)的唯一方法是提交 mutation狸眼。
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
increment(state) {
state.count ++
}
}
})
我們通過上面的代碼可以簡單地定義一個(gè) mutation 藤树,但是還需要一個(gè)方法去觸發(fā)它,這就要用到commit
方法:
store.commit('increment')
commit
可以放在組件里面來提交 mutation拓萌,也可以在 action 里面提交 mutation岁钓。
注意:mutation 只能是同步的,不支持異步微王,當(dāng)我們需要使用異步的方法的時(shí)候屡限,就需要使用 action 異步地來觸發(fā) mutation。
3.4 Actions
Action 的作用上面已經(jīng)提到了炕倘,可以稍微總結(jié)一下:
- Action 提交的是 mutation钧大,而不是直接變更狀態(tài)。
- Action 可以包含異步操作罩旋。
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
increment(state) {
state.count ++
}
},
actions: {
increment(context) {
context.commit('increment')
}
}
})
實(shí)際上啊央,Actions 的主要用處也就是封裝一些可以異步觸發(fā) mutation 的方法,組件可以使用dispatch
方法調(diào)用action涨醋,包括和 API 的通信瓜饥。
3.5 Modules
最后,來了解一下 Modules东帅,其實(shí)很容易理解,我們在寫 js 代碼的時(shí)候球拦,經(jīng)常會根據(jù)模塊進(jìn)行拆分靠闭,Modules 也是這個(gè)思想。在編寫大型應(yīng)用的時(shí)候坎炼,需要 Vuex 管理的狀態(tài)比較多的時(shí)候愧膀,就需要把 Vuex 的 Store 對象分割成 module。
const moduleA = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
一般情況下谣光,不是很復(fù)雜的項(xiàng)目我覺得并不需要拆分檩淋,也不一定要按照官方給的文件目錄來開發(fā)。只能說萄金,按方抓藥吧蟀悦。
4. 總結(jié)
關(guān)于 Vuex 的基本原理差不多就是這些了,但是由于它的特點(diǎn)氧敢,光看基本原理會發(fā)現(xiàn)什么都不會日戈,而且光看幾個(gè)小 demo 也讓人覺得它好像沒啥用,因此孙乖,學(xué)習(xí) Vuex 需要在具體的項(xiàng)目中運(yùn)用到浙炼,才能對它有一定的理解份氧。當(dāng)然,讀一讀源碼弯屈,知道它是怎么實(shí)現(xiàn)的也是很好的蜗帜。
下一章:關(guān)于 Vuex 源碼的解析,就是根據(jù)這張來自美團(tuán)點(diǎn)評技術(shù)團(tuán)隊(duì)的 Vuex.store 源碼實(shí)現(xiàn)邏輯圖來進(jìn)行的资厉,有興趣的小伙伴可以先自己把源碼下下來根據(jù)流程看一看厅缺,文章大概周日更新。