我所理解的前端狀態管理

hello,好久沒更,今天我們聊聊前端狀態管理史~

前言

我們知道,在前端發展初期,html網頁只是靜態的,任何小的改動意味着一個新的頁面;之後出現了iframe和XMLHttpRequest,實現了異步的局部加載,極大的提升了用戶體驗;再到後面的jQuery,利用「命令式」的編程管理dom的狀態,但應用一旦複雜的話也難以維護;直到近些年從Angular的誕生,到後來的React,Vue陸續推出,各類前端技術框架層出不窮,除了Vue用戶熟悉的Vuex,React與Redux,還出現了Mobx,Rxjs等其他優秀的數據存儲和管理的方案,這才真正有了前端狀態管理的概念。

不得不說,SPA的出現進一步帶動了前後端分離,前端再也不需要通過請求URL返回的HTML來顯示頁面,而是通過加載解析JS代碼生成DOM,獨立地實現了內容的渲染,在這過程中,「數據驅動」的思想也深入人心。

所謂數據驅動,是指視圖是由數據驅動生成的,我們可以通過修改數據來處理DOM,這也是典型的「聲明式」編程。以React爲例,我們可以輕鬆的通過改變state更新UI,但是有一個問題,React並不會對數據層有任何的限制,即任何組件都可以改變數據層的代碼,那就帶來了一個問題,一旦數據層出現特殊狀況,很難快速定位和解決問題,那麼如何在視圖組件中管理公共的狀態呢?

這個時候Facebook團隊提出了Flux思想,旨在從架構層面來解決MVC的在複雜場景下越來越複雜內部邏輯繁重等問題。那到底啥纔是Flux架構呢?

Flux思想及其實現

Vuex官方文檔裏一句話很有意思:"Flux 架構就像眼鏡:您自會知道什麼時候需要它。"

好吧,還是不清楚,那我們先了解一下Flux的一些基本概念。(以Facebook官方實現爲例)

Flux將一個應用分成四個部分,其中:「視圖層 View、視圖層發出的消息 Action、用來接收Actions並執行回調函數 Dispatcher、以及用來存放應用的狀態的 Store」

從上圖可以很明顯看到,這是一個「單向流動」的過程:用戶只能觸發Action來修改狀態,應用的狀態也必須放在Store裏統一管理,通過監聽Action進行一些具體的操作。任何狀態的變更都離不開Action的發起以及Dispatcher的派發,這樣一來,我們可以很容易的記錄每一次的狀態變化。這也是Flux基本的設計理念,之後在此基礎上也出現了越來越多的Flux實踐,接下來,我們以React的狀態管理髮展歷程爲例,看看Redux是怎麼管理狀態的。

(對Flux感興趣的話可以繼續看阮一峯這篇 Flux架構入門教程。)

Redux

React剛出現的時候還沒有Redux,我們只能通過state來管理組件的狀態,這在多數情況下已經足夠了,但是應用一旦複雜起來,狀態還是會變得難以維護。之後又受到Flux的影響,在這種環境下,Redux應運而生。

首先,Redux有三大原則:「單一數據源,state只讀,使用純函數來執行修改」。前兩條很好理解,至於第三條,要知道React遵循的是數據的不可變性,即永遠不在原對象上修改屬性,並且在源碼中,Redux只通過比較新舊兩個對象的存儲位置來比較新舊兩個對象是否相同,這就意味着不能直接修改state的屬性,每次只能返回新的 state。於是我們可以把Redux當作是 Reduce + Flux,而Reduce就是上面說的純函數。

不僅如此,Redux中還有一個重要的概念 Middleware。

Redux中的中間件提供的是位於 Action 被髮起之後,到達 Reducer 之前的擴展點,一圖概括:

可以看到,使用中間件時,中間件會將發起的Actions做相應的處理,最後交給Reducer執行。假設我們需要在每次觸發Action前打印log,那我們就可以將Dispatch方法拿出來重寫,大概像這樣:

let next = store.dispatch
store.dispatch = function dispatchLog(action) {  
  console.log('log', action) 
  next(action) 
}

這樣以後發出Action時,就不需要做額外的工作了。

那爲什麼要將中間件設計成這種 middleware =(store) => (next) => (action) => { [return next(action)]} 多層柯里化的寫法呢,源碼中有applyMiddleware方法用來添加中間件,其核心代碼大概是這樣:

let store = createStore(reducers);
let dispatch;
// 這裏第一次執行返回了一個存放可以修改dispatch函數的數組chain [f(next)=>acticon=>next(action)]
// 這個函數接收next參數 就是在中間件間傳遞的diapatch方法
let chain = middlewares.map(middleware => middleware({
  getState: store.getState, // 因爲閉包 執行中間件的時候store不會更新
  dispatch: action => dispatch(action)
}));
// 調用原生的dispatch重寫dispatch邏輯 只有最後一箇中間件會接受真實的dispatch方法
dispatch = compose(...chain)(store.dispatch);
return {
  ...store,
  dispatch
}


這裏可以看到中間件的設計確實十分巧妙,利用柯里化的結構「可以方便的訪問相同的store,還能夠配合compose方法,積累參數達到延遲執行的效果」,關於compose方法的說明就不列出來了,主要是通過reduce方法從右到左整合中間件。

其實Redux源碼的內容還有其他值得細細品味的地方,以後有機會再寫吧,接下來我們看一個新鮮的面孔。

下一代React狀態管理器

自從React16以來,大家都紛紛用起了Hooks,毫無疑問,Hooks的出現在一定程度上解決了組件間功能複用的問題,這種邏輯的封裝和複用確實很香,但還存在某些問題,比如說數據的共享。hox,被稱爲下一代React狀態管理器,就是爲了解決這類問題。

進入項目的Github,這裏的Features很有意思:「只有一個API;使用 custom Hooks 來定義 model;完美擁抱TypeScript;支持多數據源」。不得不說這個one API很吸引人,我們先看看官方示例:

// 定義Model
import { createModel } from 'hox';
/* 任意一個 custom Hook */
function useCounter() {
  const [count, setCount] = useState(0);
  const decrement = () => setCount(count - 1);
  const increment = () => setCount(count + 1);
  return {
    count,
    decrement,
    increment
  };
}
export default createModel(useCounter)
--------------------------------------------------------------------------------------------
// 使用Model
import { useCounterModel } from "../models/useCounterModel";


function App(props) {
  const counter = useCounterModel();
  return (
    <div>
      <p>{counter.count}</p>
      <button onClick={counter.increment}>Increment</button>
    </div>
  );
}
// 這個時候useCounterModel 是一個真正的 Hook,會訂閱數據的更新。也就是說,當點擊 “Increment” 按鈕時,會觸發 counter model 的更新,並且最終通知所有使用 useCounterModel 的組件或 Hook。

這裏的createModel類似HOC的使用,其作用就是實現數據的共享,原理大概就是其內部實現了個發佈訂閱器,每當Model進行重渲染時,會通知其訂閱者重新進行渲染。有興趣的童鞋們不妨直接去看源碼,其核心內容並不多。

(其實看到hox的時候發現和我之前寫的一個小程序版的狀態管理插件有點像,都是一個API實現全局數據共享,這裏厚臉皮的貼上Github地址 createStore。)

總結

從前端狀態管理的起源,到React的實現歷程分析,簡單的分享了一些自己的看法,文中有不夠準確的地方歡迎交流指正。

最後,520快樂,感謝閱讀!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章