目錄
- 組件化
- 組件通信
- 狀態(tài)管理
- Vuex 是什么
- Vuex 有什么特點(diǎn)
- Vuex 解決了什么問題
- 什么類型的數(shù)據(jù)適合放在 Vuex 管理
- 工具
1.組件化
Web Components提供了一種組件化的推薦方式,具體來說,就是:
- 通過shadow DOM封裝組件的內(nèi)部結(jié)構(gòu)
- 通過Custom Element對外提供組件的標(biāo)簽>
- 通過Template Element定義組件的HTML模板
- 通過HTML imports控制組件的依賴加載
所謂組件化,核心意義莫過于提取真正有復(fù)用價(jià)值的東西。那怎樣的東西有復(fù)用價(jià)值呢?
- 控件
- 基礎(chǔ)邏輯功能
- 公共樣式
- 穩(wěn)定的業(yè)務(wù)邏輯
對組件的粒度進(jìn)行細(xì)分,可以分為:
- UI component: 純 UI 組件,可以維護(hù)本地的 UI State,接收 props 作為數(shù)據(jù)渲染,保持純函數(shù)形式,具有可復(fù)用性。
- Logic component: 帶有邏輯的 UI 組件,與數(shù)據(jù)打交道。
組件化這個(gè)詞,在 UI 這一層通常指“標(biāo)簽化”,也就是把大塊的業(yè)務(wù)界面,拆分成若干小塊,然后進(jìn)行組裝。狹義的組件化一般是指標(biāo)簽化,也就是以自定義標(biāo)簽(自定義屬性)為核心的機(jī)制。廣義的組件化包括對數(shù)據(jù)邏輯層業(yè)務(wù)梳理,形成不同層級的能力封裝。
2.組件通信
應(yīng)用在組件化之后,組件之間必然存在某種聯(lián)系;組件化意味著協(xié)同工作,通常存在著 父子組件、兄弟組件、跨級組件等組件關(guān)系,那么組件之間如何進(jìn)行協(xié)調(diào)工作,即組件通信;
在 Vue 中,父子組件的關(guān)系可以總結(jié)為 props down、events up。
-
父子組件通信:父組件通過 props
向下傳遞數(shù)據(jù)給子組件 -
子父組件通信:子組件通過 events
-- - 給父組件發(fā)送消息使用$on(eventName)監(jiān)聽事件
-- - 使用$emit(eventName)觸發(fā)事件
非父子組件通信:使用一個(gè)空的 Vue 實(shí)例作為中央事件總線
可以想象到在簡單的 父子,子父 組件之間的通信是很輕松的,通過 props 和 events 即可實(shí)現(xiàn);但是往往我們的應(yīng)用可能不只有這么簡單的層級關(guān)系,在多層跨級組件如果通過 props 去傳遞,那意味著一層一層的往子組件傳遞,最終你可能不知道當(dāng)前組件的數(shù)據(jù)最終來自哪個(gè)父組件(當(dāng)然你可以逆著方向一層一層往上找),通過 events 事件機(jī)制顯然也存在著類似的問題。如果你覺得這樣也可以接受,你可能不需要 Vuex;但如果你在想有沒有什么好的模式優(yōu)雅的去解決,你可以繼續(xù)閱讀下面的部分。
3.狀態(tài)管理
隨著 JavaScript 單頁應(yīng)用開發(fā)日趨復(fù)雜,JavaScript 需要管理比任何時(shí)候都要多的 state (狀態(tài))。 這些 state 可能包括服務(wù)器響應(yīng)、緩存數(shù)據(jù)、本地生成尚未持久化到服務(wù)器的數(shù)據(jù),也包括 UI 狀態(tài),如激活的路由,被選中的標(biāo)簽,是否顯示加載動(dòng)效或者分頁器等等。
管理不斷變化的 state 非常困難。如果一個(gè) model 的變化會引起另一個(gè) model 變化,那么當(dāng) view 變化時(shí),就可能引起對應(yīng) model 以及另一個(gè) model 的變化,依次地,可能會引起另一個(gè) view 的變化。直至你搞不清楚到底發(fā)生了什么。state 在什么時(shí)候,由于什么原因,如何變化已然不受控制。 當(dāng)系統(tǒng)變得錯(cuò)綜復(fù)雜的時(shí)候,想重現(xiàn)問題或者添加新功能就會變得舉步維艱。 -- 摘自《Redux 中文文檔》
在應(yīng)用中,組件之間的通信其實(shí)是歸根于應(yīng)用的狀態(tài)管理;而應(yīng)用的狀態(tài)是來自多方面的,如何對狀態(tài)進(jìn)行管理,提高代碼的可維護(hù)性,提升開發(fā)效率;大多數(shù)主流框架對數(shù)據(jù)狀態(tài)管理也都有了對應(yīng)的方案:
- React 專注于 UI 層,社區(qū)為其提供了 Redux、Mbox 等狀態(tài)管理庫
- Vue 的團(tuán)也提供了 Vuex 狀態(tài)管理庫
- 還有一些專門解決數(shù)據(jù)層的庫,如 RxJS
回到本文的討論點(diǎn),這里我們暫且只討論 Vue;Vue 的核心庫只關(guān)注視圖層,單文件組件,其模板、邏輯和樣式是內(nèi)部耦合的,側(cè)重?cái)?shù)據(jù)和視圖的同步;Vue 本身并沒有對數(shù)據(jù)狀態(tài)的管理進(jìn)行處理,但其提供了另外一個(gè)類似 Redux 的解決方案 Vuex,一個(gè)集中式狀態(tài)管理的庫;也就是說,你可能不需要 Vuex,它只是對你應(yīng)用狀態(tài)進(jìn)行管理的一個(gè)庫。
4.Vuex 是什么
Vuex 是一個(gè)專門為 Vue.js 應(yīng)用所設(shè)計(jì)的集中式狀態(tài)管理架構(gòu)。
Vuex 是一個(gè)專為 Vue.js 應(yīng)用程序開發(fā)的狀態(tài)管理模式。它采用集中式存儲管理應(yīng)用的所有組件的狀態(tài),并以相應(yīng)的規(guī)則保證狀態(tài)以一種可預(yù)測的方式發(fā)生變化。
- 集中式狀態(tài)管理模式(注意是強(qiáng)調(diào)管理應(yīng)用的所有組件的狀態(tài))
- 可預(yù)測(前提是以相應(yīng)的規(guī)則作為保證)
5.Vuex 有什么特點(diǎn)
從上面的定義中可以知道 Vuex 的特點(diǎn)其實(shí)就是下面兩點(diǎn):
- 集中式狀態(tài)管理
- 可預(yù)測
什么是集中式狀態(tài)管理模式
在說集中式管理模式之前,我們可以先來想想常見的處理方式是怎樣的,即每個(gè)組件維護(hù)自身的數(shù)據(jù)和狀態(tài),自給自足,分而治之;其思路大致如下:
- 定義組件自身的初始數(shù)據(jù)
- 在組件內(nèi)獲取異步數(shù)據(jù)
- 根據(jù)數(shù)據(jù)渲染更新視圖
// 渲染視圖
<template>
<h2>single file component</h2>
<template>
<script>
export default {
// 初始數(shù)據(jù)
data() {
},
// 獲取異步數(shù)據(jù)
created() {
this.fetchData()
},
methods {
fetchData() {
// do something
}
}
}
</script>
分治帶來的是可管理性,把組件設(shè)想成一個(gè)單一的東西,一個(gè)組件包含了自身需要的數(shù)據(jù)和視圖,把查詢邏輯封裝在內(nèi)部,外部只需要實(shí)現(xiàn)一個(gè)響應(yīng)事件獲取事件的東西基于可以了。即 Single File Component 概念,組件化后,整個(gè)應(yīng)用的樹結(jié)構(gòu)可以一目了然,可以隨意添加或者移除一個(gè)組件,而不會影響其他的組件,聽起來很美好;但事情并非那么完美,由于這種方式封裝的組件的內(nèi)部實(shí)現(xiàn)聚合了異步請求的數(shù)據(jù)和自身的狀態(tài),真正組裝復(fù)用起來是存在一定問題的。比如:
- 在同一可視區(qū)域的冗余請求數(shù)
-
不同層級組件的數(shù)據(jù)共享問題
集中式狀態(tài)管理模式則以一個(gè)全局單例模式管理應(yīng)用的狀態(tài),類似于全局對象,但不完全一樣。
-
Vuex 的狀態(tài)管理存儲是響應(yīng)式的:就是當(dāng)你的組件使用到了 Vuex 的某個(gè)狀態(tài),一旦它發(fā)生改變了,所有關(guān)聯(lián)的組件都會自動(dòng)更新相對應(yīng)的數(shù)據(jù)。
-
不能直接修改 Vuex 的狀態(tài):修改 Vuex 的狀態(tài)唯一途徑是提交(commit) mutations 來實(shí)現(xiàn)修改
如上圖,Vuex為Vue Components建立起了一個(gè)完整的生態(tài)圈,包括開發(fā)中的API調(diào)用一環(huán)。圍繞這個(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,界面隨之更新
集中式狀態(tài)管理的好處
相對于分治(碎片化)的狀態(tài)管理,多個(gè)狀態(tài)分散的跨越在不同組件交互在各個(gè)角落,每個(gè) View 會有相對應(yīng)的 Model 維護(hù)狀態(tài);而集中式管理模式則用于將分散于組件的狀進(jìn)行集中化管理,提供一個(gè)全局的 store 存儲管理應(yīng)用的狀態(tài)。集中式的狀態(tài)管理可以讓整體的狀態(tài)變化更加明晰,尤其是配合各自的 devtools。它具備以下特點(diǎn):
- components share state(組件之間共享狀態(tài))
- state should be accessible from everywhere(所有狀態(tài)可以方便獲取)
- components need to mutate the state(組件可以修改狀態(tài))
- components need to mutate the state of another component(組件可以修改其他組件的狀態(tài))
集中式狀態(tài)管理的弊端
上面提到集中式存儲管理應(yīng)用的所有組件的狀態(tài),而應(yīng)用的狀態(tài)上文已經(jīng)有提到,這里大致可以分為:
- UI 狀態(tài):用戶輸入的狀態(tài)
- 數(shù)據(jù)狀態(tài):服務(wù)端傳過來的數(shù)據(jù)狀態(tài)
- 客戶端信息:設(shè)備信息的狀態(tài)
- 其他...
這里是有歧義的,集中式存儲管理應(yīng)用的所有狀態(tài),按照字面意思是將所有的狀態(tài)都集中式管理,也就是存到 Vuex 的全局單一 store 中,顯然我們是不能這樣去理解的,應(yīng)該視應(yīng)用場景而定的,大致也可以分為以下幾種:
對于用戶輸入的狀態(tài),比如控制模態(tài)框的顯示隱藏,我們一般在組件內(nèi)處理消化;對于需要需要跨組件通信的,則可以存儲在全局的 store 中,我們可以將這一類狀態(tài)稱之為本地狀態(tài)(local state)。
對于服務(wù)端傳過來的數(shù)據(jù)狀態(tài),按照大多數(shù)的實(shí)踐是存儲在全局的 store 中,這樣可以在任意的組件中都可以使用;當(dāng)然,也可以只將多組件的共享的數(shù)據(jù)存儲在全局的 store 中,單個(gè)組件需要的數(shù)據(jù)內(nèi)部處理消化,組件銷毀時(shí)對應(yīng)的數(shù)據(jù)狀態(tài)也會銷毀。
對于客戶端的信息或者一些其他的數(shù)據(jù)狀態(tài)與上面兩種方式在一定程度上也是相似的。這一切看起來并沒什么問題,然而細(xì)細(xì)想想,當(dāng)一個(gè)應(yīng)用的足夠復(fù)雜時(shí),我們該如何去設(shè)計(jì)我們的數(shù)據(jù)模型,本地共享的狀態(tài)是存在 store 還是通過事件機(jī)制去處理,服務(wù)端的數(shù)據(jù)是一股腦都塞給全局的 store 存在內(nèi)存里還是視應(yīng)用場景而定,在 Vuex 的文檔或者是 Redux 文檔這都沒有唯一的答案。假設(shè)服務(wù)端傳過來的數(shù)據(jù)都存在 store, 那最終的 store 會有多大,這是一個(gè)值得探索的問題。那究竟什么樣的數(shù)據(jù)適合存儲在全局的 store 中?
另外,使用 Vuex 必須按照上述 Vuex 的工作流程去進(jìn)行,定義對應(yīng)的 actions, mutations等等,這顯然是在強(qiáng)制約定你以相應(yīng)的規(guī)則去編寫你的應(yīng)用,對大多數(shù)新人來說,這是繁瑣的。就相當(dāng)于你得到了一定的好處,那你也得相應(yīng)的有所付出。
什么類型的數(shù)據(jù)適合放在 Vuex 管理
至此,我們大概討論了由于組件化,會產(chǎn)生組件間相互通信數(shù)據(jù)管理的問題,對此也有相應(yīng)了的解決辦法;然而,并沒有一種很好的方式告訴我們到底什么類型的數(shù)據(jù)適合放在單一的 store 進(jìn)行管理;回到 Vuex 的定義,將數(shù)據(jù)使用 Vuex 管理的主要原因之一是解決組件間的數(shù)據(jù)共享。
所謂共享指的是,同一份數(shù)據(jù)被多處組件使用,并且要保持一定程度的同步:
故而,在開發(fā)應(yīng)用時(shí),如何設(shè)計(jì)抽象數(shù)據(jù)層,這個(gè)是沒有唯一答案的。但如果明白了其間的利弊,比如獨(dú)立了數(shù)據(jù)層,視圖的職責(zé)就非常單一,無非就是根據(jù)訂閱的數(shù)據(jù)渲染頁面,視圖組件間的通信就會很少,大家都會去跟數(shù)據(jù)層交互,維護(hù)一份統(tǒng)一的數(shù)據(jù)結(jié)構(gòu)。
工具
到這里,你應(yīng)該可以確定你的應(yīng)用是否應(yīng)該使用 Vuex 了,如果你使用了 Vuex,那么它還有一些其他的附屬產(chǎn)品;如下面的 vue-tool 調(diào)試工具,它可以讓你對你的應(yīng)用狀態(tài)了如指掌,保存狀態(tài)快照,歷史回滾/時(shí)光旅行等等特性。
總結(jié)
合久必分,分久必合;Vue 提倡 Single File Component 概念將單一功能進(jìn)行組件化封裝,而 Vuex 的設(shè)計(jì)則是將分散在各處的狀態(tài)進(jìn)行合并集中管理的抽象模式。利弊在上面的文章也已說明,它是一種可選的方案,你用或者不用,取決于你的應(yīng)用。


