目錄
- 1.背景
- 2.一個常見的多個組件共享狀態的問題
- 3.MVC 架構
- 4.Flux 架構
- 5.MVC 與 Flux 的對比
- 6.Vue.js 的 Vuex 方案
- 7.使用 Vuex 的正確姿勢
- 8.只有 Vuex 還不夠...
- 9.結語
- 10.推薦閱讀
1.背景
Web 頁面開發的應用化趨勢
隨着 Web 能力的不斷提升,用戶的 Web 頁面體驗的要求,要求像原生一樣,無刷新、離線緩存等和原生應用的功能。
Web 開發已經進入組件化時代
2.一個常見的多個組件共享狀態的問題
在下圖中播放器組件上點贊以後,如何同步更新其他地方有關這個視頻的點贊信息,比如右側信息區組件、詳情頁?
可能的解決方案
- 方案 1:刷新頁面
- 方案 2:把所有涉及到的接口都重新請求一遍
- 方案 3: 直接更改 Dom
...
這些方案的問題
- 方案 1: 體驗差,會丟失用戶的當前操作狀態
- 方案 2: 多餘的請求,網絡請求需要耗時
- 方案 3:很難維護,也容易遺漏
問題的根源:
不同視圖上的相同數據沒有一個統一的來源,如果點贊信息能統一存儲,只需要更新同一個地方,並且都從這個地方獲取信息顯示到視圖上,就不會有這個問題了
3.MVC 架構
MVC 模式(Model–view–controller)是軟件工程中的一種軟件架構模式,把軟件系統分爲三個基本部分:模型(Model)、視圖(View)和控制器(Controller)。
比如點贊信息作爲一個 Model, 每個視圖都可以讀取這個 Model,然後視圖通過通知 Controller 去更新 Model,視圖通過監聽 Model 的變化再進行同步。
4.Flux 架構
Flux 是由 Facebook 提出的,用於組織應用的一種架構,它基於一個簡單的原則:數據在應用中單向流動。這就是所謂的“單向數據流”
,簡單的記法是把數據比作鯊魚:鯊魚只能向前遊。
Flux 試圖通過強制單向數據流來解決這個複雜度。在這種架構當中,Views 查詢 Stores(而不是 Models),並且用戶交互將會觸發 Actions,Actions 則會被提交到一個集中的 Dispatcher 當中。當 Actions 被派發之後,Stores 將會隨之更新自己並且通知 Views 進行修改。這些 Store 當中的修改會進一步促使 Views 查詢新的數據。
5.MVC 與 Flux 的對比
EventBus 數量
- MVC: 一個 model 一個
- Flux: 只有一個
視圖同步數據方式
- MVC:view 監聽 model 的事件
- Flux: model 的改變自動同步到視圖
查詢和寫入數據的區別
- MVC:Model 暴露給 View,View 有可能會更新 model,像 backbone 就可以在 View 裏調用
this.model.set()
- Flux: View 從 Store 獲取的數據是隻讀的, Stores 只能通過 Actions 被更新
數據改變的可感知性
- MVC:由於有多個 EventBus,所以很難去定位改變了 model 的具體來源
-
Flux: 任何狀態的變化都必須通過 action 觸發,而 action 又必須通過 dispatcher 走,所以整個應用的每一次狀態變化都會從同一個地方流過
6.Vuex
Vuex 是一個專爲 Vue.js 應用程序開發的狀態管理模式。它採用集中式存儲管理應用的所有組件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化。
借鑑了 Flux、Redux、和 The Elm Architecture。與其他模式不同的是,Vuex 是專門爲 Vue.js 設計的狀態管理庫,以利用 Vue.js 的細粒度數據響應機制來進行高效的狀態更新。
多個組件共享狀態的問題
由於 Flux 架構有多個 store,我們的應用遇到多個組件共享狀態時,單向數據流的簡潔性很容易被破壞:
- 多個視圖依賴於同一狀態。
- 來自不同視圖的行爲需要變更同一狀態。
問題的解決
- 對於問題一,傳參的方法對於多層嵌套的組件將會非常繁瑣,並且對於兄弟組件間的狀態傳遞無能爲力。
- 對於問題二,我們經常會採用父子組件直接引用或者通過事件來變更和同步狀態的多份拷貝。以上的這些模式非常脆弱,通常會導致無法維護的代碼。
最終解決方案
因此,我們爲什麼不把組件的共享狀態抽取出來,以一個全局單例模式管理呢?在這種模式下,我們的組件樹構成了一個巨大的“視圖”,不管在樹的哪個位置,任何組件都能獲取狀態或者觸發行爲!
另外,通過定義和隔離狀態管理中的各種概念並強制遵守一定的規則,我們的代碼將會變得更結構化且易維護。
這就是 Vuex 背後的基本思想。
7.使用 Vuex 的正確姿勢
背景:前端數據來源的多樣性
不同端的數據來源多樣性
- 服務端接口
- 前端用戶的輸入
- 客戶端信息(比如設備信息等)
同一端數據來源的多樣性
如果服務端的接口不是基於 restful 設計,或者接口內容是一些聚合的接口,比如服務端設計一個獲取用戶信息接口,返回了用戶的 vip 信息、關注信息、粉絲信息等,同時設計了分別的獲取 vip 信息接口、關注信息、粉絲信息接口,這樣同一個內容在服務端這一端會有不同的來源。
如果前端組件之間對同一個狀態的讀取和操作沒有做到複用,假設一個評論列表組件和彈幕組件之間,都用到了用戶的評論信息,如果沒有做到統一讀取和更新,就會導致同樣的狀態信息有兩個數據來源,最終導致同步問題。
數據讀取,保證讀取單一狀態來源
需要保證不同的 View 或者組件讀取某個狀態信息都是從同一個地方讀取,所以,迴歸到 Vuex 設計的本意上來,我們應該把同一狀態信息集中管理。
數據存儲,從聚合到原子
思路
- 服務端儘量提供原子化的接口,比如 restful 風格
- 前端數據層設計成原子化,不存儲聚合信息,所有的聚合信息都拆開存儲
前端數據存儲原子化設計
下面是一個組隊加速需求的狀態設計:
{
users: {
[uid]: {
userid: String,
nickName: String,
avatar: String,
// 存儲team的索引
teams: String[]
}
},
teams: {
[groupId]: {
teamId: String,
taskId: String,
// 存儲user的索引
users: String[]
}
},
tasks: {
[taskID]: {
taskID: String,
name: String,
gcid: String,
status: String,
suffix: String,
teams: String[]
}
},
increases: {
[gcid]: {
[userId]: {
uploadAmount: String,
increaseAmount: String
}
}
}
}
數據結構設計原則
- 避免嵌套數據,數據結構扁平化
- 雙向關係存儲索引,適當的冗餘來提升讀取的效率
8.有了 Vuex 還需要什麼?
數據校驗設計
Vuex 對 state 的寫入沒有統一的校驗機制,複雜的單頁面應用應保證 state 內存儲的數據結構和類型都是一致的,可以在 Vuex 這一層做一層類似組件的 props 驗證那樣的驗證機制。
數據緩存設計
需要一套完整的緩存機制,包括緩存的讀、寫及過期清理邏輯。
- 持久化存儲
- localStorage
- 會話期的緩存
- 內存
- sessionStorage
- 跨頁面會話期存儲
- localStorage
數據同步設計
- 獨立同步
- 合併同步
- 聚合同步
數據同步頻率控制設計
就像一窩蜂的人去排隊看演出,隊伍很亂,看門的老大爺每隔 1 秒,讓進一個人,這個叫 throttle,如果來了這一窩蜂的人,老大爺一次演出只讓進一個人,下次演出才讓下一個人進,這個就叫 debounce
- throttle:節流,控制單位時間內對特定內容的同步請求的最大次數
- debounce:去抖,連續的對同一個內容的同步請求我們只需要執行一次