寫這篇文章的主要目的是在如今的公司推薦使用 Vue,而在使用 Vue 的時候不少同事對爲何要使用 Vuex 不理解,我自己是沒有使用過 Vue 或者 Vuex 寫過實際項目;有過一年左右的 React 和 Redux 相關技術的項目實踐,主要是根據對等的一點經驗所寫;不當之處,歡迎討論。html
組件化vue
組件通訊react
狀態管理git
Vuex 是什麼github
Vuex 有什麼特色web
Vuex 解決了什麼問題vuex
什麼類型的數據適合放在 Vuex 管理redux
工具segmentfault
總結
參考
擴展閱讀
Web Components提供了一種組件化的推薦方式,具體來講,就是:
經過shadow DOM封裝組件的內部結構
經過Custom Element對外提供組件的標籤
經過Template Element定義組件的HTML模板
經過HTML imports控制組件的依賴加載
所謂組件化,核心意義莫過於提取真正有複用價值的東西。那怎樣的東西有複用價值呢?
控件
基礎邏輯功能
公共樣式
穩定的業務邏輯
-- 摘自xufei的《2015前端組件化框架之路》
對組件的粒度進行細分,能夠分爲:
UI component: 純 UI 組件,能夠維護本地的 UI State,接收 props 做爲數據渲染,保持純函數形式,具備可複用性。
Logic component: 帶有邏輯的 UI 組件,與數據打交道。
組件化這個詞,在 UI 這一層一般指「標籤化」,也就是把大塊的業務界面,拆分紅若干小塊,而後進行組裝。狹義的組件化通常是指標籤化,也就是以自定義標籤(自定義屬性)爲核心的機制。廣義的組件化包括對數據邏輯層業務梳理,造成不一樣層級的能力封裝。
應用在組件化以後,組件之間必然存在某種聯繫;組件化意味着協同工做,一般存在着 父子組件
、兄弟組件
、跨級組件
等組件關係,那麼組件之間如何進行協調工做,即組件通訊;
在 Vue 中,父子組件的關係能夠總結爲 props down
、events up
。
父子組件通訊:父組件經過 props
向下傳遞數據給子組件
子父組件通訊:子組件經過 events
給父組件發送消息
使用 $on(eventName)
監聽事件
使用 $emit(eventName)
觸發事件
非父子組件通訊:使用一個空的 Vue 實例做爲中央事件總線
能夠想象到在簡單的 父子
,子父
組件之間的通訊是很輕鬆的,經過 props
和 events
便可實現;可是每每咱們的應用可能不僅有這麼簡單的層級關係,在多層跨級組件若是經過 props
去傳遞,那意味着一層一層的往子組件傳遞,最終你可能不知道當前組件的數據最終來自哪一個父組件(固然你能夠逆着方向一層一層往上找),經過 events
事件機制顯然也存在着相似的問題。若是你以爲這樣也能夠接受,你可能不須要 Vuex;但若是你在想有沒有什麼好的模式優雅的去解決,你能夠繼續閱讀下面的部分。
隨着 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,它只是對你應用狀態進行管理的一個庫。
Vuex 是一個專門爲 Vue.js 應用所設計的集中式狀態管理架構。
-- from vuex docs (updated in 2016-05-27)
...
Vuex 是一個專爲 Vue.js 應用程序開發的狀態管理模式。它採用集中式存儲管理應用的全部組件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化。
-- from vuex docs (updated in 207-05-20)
上面的定義是摘自 Vuex 在 GitHub 上的文檔,分別於 2016/05/27 和 2017/05/20 的更新記錄;從中咱們能夠梳理一下關鍵詞:
集中式狀態管理模式(注意是強調管理應用的全部組件的狀態)
可預測(前提是以相應的規則做爲保證)
先不着急看下面的內容,若是你看到以上兩點特性,腦海已經有了答案,能很好的向你的同事解釋清楚,前提是你的同事徹底沒有任何 Vuex 的經驗,那下面的內容你能夠直接忽略。你也應該知道你的應用是否須要 Vuex 了。
從上面的定義中能夠知道 Vuex 的特色其實就是下面兩點:
集中式狀態管理
可預測
在說集中式管理模式以前,咱們能夠先來想一想常見的處理方式是怎樣的,即每一個組件維護自身的數據和狀態,自給自足,分而治之;其思路大體以下:
定義組件自身的初始數據
在組件內獲取異步數據
根據數據渲染更新視圖
// 渲染視圖 <template> <h2>single file component</h2> <template> <script> export default { // 初始數據 data() { }, // 獲取異步數據 created() { this.fetchData() }, methods { fetchData() { // do something } } } </script>
可簡單對比 React 的思路:
// https://github.com/xufei/blog/issues/19#issuecomment-85989838 var render = function(Model) { return View; } event_loop(function(){ var currentView = render(currentModel); map_view_into_user_interface(currentView); })
分治帶來的是可管理性,把組件設想成一個單一的東西,一個組件包含了自身須要的數據和視圖,把查詢邏輯封裝在內部,外部只須要實現一個響應事件獲取事件的東西基於能夠了。即 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,界面隨之更新
建議類比 Redux 的流程,大致上是相同的:
相對於分治(碎片化)的狀態管理,多個狀態分散的跨越在不一樣組件交互在各個角落,每一個 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
等等,這顯然是在強制約定你以相應的規則去編寫你的應用,對大多數新人來講,這是繁瑣的。就至關於你獲得了必定的好處,那你也得相應的有所付出。
至此,咱們大概討論了因爲組件化,會產生組件間相互通訊數據管理的問題,對此也有相應了的解決辦法;然而,並無一種很好的方式告訴咱們到底什麼類型的數據適合放在單一的 store 進行管理;回到 Vuex 的定義,將數據使用 Vuex 管理的主要緣由之一是解決組件間的數據共享。
所謂共享指的是,同一份數據被多處組件使用,而且要保持必定程度的同步:
故而,在開發應用時,如何設計抽象數據層,這個是沒有惟一答案的。但若是明白了其間的利弊,好比獨立了數據層,視圖的職責就很是單一,無非就是根據訂閱的數據渲染頁面,視圖組件間的通訊就會不多,你們都會去跟數據層交互,維護一份統一的數據結構。
到這裏,你應該能夠肯定你的應用是否應該使用 Vuex 了,若是你使用了 Vuex,那麼它還有一些其餘的附屬產品;以下面的 vue-tool 調試工具,它可讓你對你的應用狀態瞭如指掌,保存狀態快照,歷史回滾/時光旅行等等特性。
合久必分,分久必合;Vue 提倡 Single File Component
概念將單一功能進行組件化封裝,而 Vuex 的設計則是將分散在各處的狀態進行合併集中管理的抽象模式。利弊在上面的文章也已說明,它是一種可選的方案,你用或者不用,取決於你的應用。
注:對組件化和狀態管理方面主要參考徐飛的系列文章,嚴重推薦深度閱讀徐飛關於 《web 應用》 和 《隨筆系列》 文章