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

總結
合久必分,分久必合;Vue 提倡 Single File Component 概念將單一功能進行組件化封裝,而 Vuex 的設計則是將分散在各處的狀態進行合并集中管理的抽象模式。利弊在上面的文章也已說明,它是一種可選的方案,你用或者不用,取決于你的應用。