web 開發中無處不在的狀態機
學過編譯原理的人都知道,詞法分析和語法分析都用到了狀態機,那是不是說狀態機就和編譯原理一樣那麼難學么?
答案是 no。狀態機實際上在生活、網頁、app、小程序中無處不在。
我這篇文章就以淺顯易懂的方式講解 web 開發中的狀態機,並談談前端框架、spa。
狀態機基本概念介紹
首先我們簡單的介紹一下狀態機的基本概念
wiki:有限狀態機(英語:finite-state machine,縮寫:FSM)又稱有限狀態自動機,簡稱狀態機,是表示有限個狀態以及在這些狀態之間的轉移和動作等行為的數學模型。
常見而簡單的狀態機如下:
圖 from 阮一峰 JavaScript與有限狀態機
我們用公式抽象一下:
末狀態 = 初狀態 + 觸發條件n
or
nextState = oldState + actionn
(是不是很像react,redux,對的其實react redux 就是管理狀態機的一種方式,後面會介紹)
狀態機的特徵
我們觀察一下上面的內容可以發現,狀態機的三個特徵
- 狀態總數是有限的。
- 任一時刻,只處在一種狀態之中。
- 某種條件觸發後,會從一種狀態轉變到另一種狀態。
我們用 wiki 里的 展示了接受單詞"nice"的有限狀態自動機 例子來更具體了解一下狀態機的實際運作過程(這是最簡單的詞法分析)。
圖來自 wikihttps://zh.wikipedia.org/wiki/%E6%9C%89%E9%99%90%E7%8A%B6%E6%80%81%E6%9C%BA
web 開發中的狀態機
言歸正傳,介紹了一些基本的狀態機知識後,我們來談談 web 開發中的狀態機
據說一圖勝千言,於是我畫了一個很醜的圖。。。
(點擊就有放大高清的圖片,我畫了半個小時orz,你們不點我不服)
簡單解釋一下這個圖
首先這個圖裡有三種級別的狀態機:
- xx 系統
- xx 頁面
- xx 組件
有三種 action:
- 點擊鏈接:觸發了系統的 state change
- 用戶交互:觸發了組件or頁面的 state change
- 系統推送:(這個我沒畫到圖裡面),像 消息 這種 state 它在一個頁面里自動的 change 就屬於系統推送
前端開發與狀態機
我們先從 js 實現一個最簡單的狀態機講起。
js 實現一個最簡單的狀態機
這一個實現 from 阮一峰的文章 JavaScript與有限狀態機
它對JavaScript的意義在於,很多對象可以寫成有限狀態機。
舉例來說,網頁上有一個菜單元素。滑鼠懸停的時候,菜單顯示;滑鼠移開的時候,菜單隱藏。如果使用有限狀態機描述,就是這個菜單只有兩種狀態(顯示和隱藏),滑鼠會引髮狀態轉變。
var menu = {n // 當前狀態n currentState: hide,n n // 綁定事件n initialize: function() {n var self = this;n self.on("hover", self.transition);n },n n // 狀態轉換n transition: function(event){n switch(this.currentState) {n case "hide":n this.currentState = show;n doSomething();n break;n case "show":n this.currentState = hide;n doSomething();n break;n default:n console.log(Invalid State!);n break;n }n }n n };n
我們可以發現上面代碼的的問題,比較低效和笨拙,把簡單問題複雜化了。
所以,問題規模小的時候,if-else 就可以工作的很好了,但當問題規模一大,無論是大量的 if-else 還是問題 解決方案的本身 將會複雜龐大難以維護,這時候引入狀態機的概念就非常行之有效了,並且當我們把狀態機的實現交給 react redux 這種庫上時,我們其實只用關心解決方案的本身,而不用自己去實現狀態機。當我們只用關心解決方案本身的時候,解決問題的難度毫無疑問是變簡單了的。
狀態機和 前端框架
在 react、redux 不在的年代,其實我們使用 jquery 寫 if-else 的時候,實際上就是在 自己造了一個 狀態機並管理著,當代碼量小的時候可能 jqurey if-else 比 react redux 這種寫起來簡單方便,但當狀態機的規模大了的時候,同樣也會發生上面所說的問題,相信用 jquery 寫過單個頁面 js 1000 行的人都深有感觸,這時候使用 jqurey 寫 if-else 的複雜度維護性都會達到一個可怕的難度上,當然這時候不少人會引入 MVC 類庫 backbone 等,但 MVC 畢竟不是 reactive (響應式)思想的框架,從思想上看還是遠不及 MVVM、react 這種響應式的編程方式簡單和方便。
這也體現在前端框架之爭中,jquery backbone 逐漸被 angular、vue、react 所代替,因為後者是更優的解決方案。
在 react 中,當問題的複雜性較小的時候,我們一般都不會去引入 redux ,因為此時 「Local state is fine」(redux 作者在 You Might Not Need Redux 的文章里這麼說道 ),因為 redux 其實是一個複雜的 狀態機的管理方案,它對於大規模的狀態機管理是優於 react 的,但對於小規模的狀態機,react 的 「Local state is fine」。
除了 狀態機管理 的方面看 前端框架,「Local state is fine」 其實還有另一層含義。
local 是什麼意思呢?
local 其實說的是 組件中的局部的意思,這裡其實有著局部 vs 全局的一個大問題,在 angular、vue、react 中,組件的開發模式是一種更優的局部,或者說 jquery 其實是一種全局操作,雖然可以用各種方法來仿造去寫局部的組件,但也是一種偽局部的組件,局部 vs 全局這裡就不展開講了,對於組件我們當然是希望它本身必須是 高內聚低耦合的,所以局部化是必須的。
所以我的觀點是,新老前端框架最大的三個區別的點(以致於出現新老交替這種劃時代技術浪潮):
- 狀態機的更優管理方式
- 組件局部化的更優編寫方式
- 響應式思想(實際上也就是 data driven view)
狀態機、 spa、後端漫談
當使用 spa 來開發系統級別的應用時,即便這個系統很小很小,可能就是一個簡單的個人博客(比如我的博客 SimplyY 的博客:所有文章 ) ,都會引入巨大的複雜度,幾乎必須要使用 redux 這種屠龍刀的技術,但對於 個人博客這種小系統,未免不會覺得有 高射炮打蚊子的感覺,還記得阿里 p9 交叉面試官對我的博客的技術選型提出的質疑,「你博客使用 react 、redux、spa 的技術選型是不是有問題,把簡單問題複雜化了」 。這其實是一個非常好的問題,spa 其實把系統級別的 狀態管理引入到了前端,我們還是看之前那張圖
在一個系統里,頁面和頁面之間狀態轉換,是通過用戶點擊鏈接來的,這時候狀態的轉換其實承載到了鏈接上。
不要小看這個鏈接,當使用 spa 時,點擊鏈接實際上就得變成用戶交互這一種 action 而不是跳轉到新的頁面,本來我們的 js 只需要管理一個頁面的 狀態,但 spa 後,我們的 js 就需要管理整個系統的狀態了。後端路由交給了前端,每點擊一下鏈接再也不是 新返回一個 html,而是 繼續讓前端響應並管理整個狀態機。其實這也是 C/S 架構 和 B/S 架構的巨大區別,而對於 spa 系統就和傳統客戶端開發,將切換頁面當做 action 都是在客戶端進行狀態管理,這時 spa 系統就和傳統客戶端 沒什麼太大區別了,這句話不是我臆測出來的,對於我參與開發過的 electron 客戶端應用其實就是一個 spa 系統,據我所知大部分的 electron 客戶端都是 spa 的,所以 spa 系統其實更像是 C/S 架構。
退一步來說,實際上對於系統級別的狀態機,對於 B/S 架構,是通過 瀏覽器 + 伺服器端一起管理的,而 spa 這是將後端的活前移到了前端。而且你要知道,更重要的是,在傳統前端 js 開發里可沒有 瀏覽器請求 html 並解析 html 的功能、數據層管理,無論是新的概念的引入,還是(在某種意義上)完成瀏覽器的活,對於相同應用,spa 是比 傳統 B/S 架構更複雜。
但更複雜難道就意味著不做嗎,那就不對了。一切還是要按照業務需求來,技術是服務於業務的。spa 意味著更好的性能,而且我們不一定是要做整站整個系統的 spa,我們可以做局部 spa。這樣用戶點擊鏈接的操作就不需要重新走一遍 瀏覽器輸入 url 的整個操作(我稱之為"刷頁面"),而直接交給我們的 js,這樣就不會有「刷頁面」這種交互體驗不好的情況,這種不「刷頁面」的需求在對於性能、體驗要求更高的移動端尤為重要,對於某些 pc 頁面也同樣有這樣的需求。
推薦閱讀: