原文:How to visually design state in JavaScriptjavascript
做者:Shawn McKayjava
譯者:逆圖ios
一份教你使用狀態機和狀態圖來開發應用的路線圖git
爲何狀態管理在JavaScript中顯得特別困難?是現代應用繼承的複雜性,仍是工具的問題?其餘工程領域是如何開發可靠和可預測的系統?是否有可能設計一個系統並將它轉換成代碼,反之亦然?github
讓咱們探索狀態管理中的範式轉換【譯者注:關於範式轉換,請戳這裏】,用狀態機和狀態圖來直觀的設計系統。web
我接觸狀態管理已經有一段時間了。並且已經嘗試過了各類各樣的狀態管理庫:Flux,Reflux,Redux,Dva,Vuex,Mobx以及我本身的庫。typescript
爭論哪個庫是更高效的解決方案是沒有意義的。各個狀態管理庫雖然有着不一樣的口味但卻有着相同的配方。它們都是整個拼圖中的一部分————它們讓鏈接和同步數據變得更加容易。編程
接下來咱們所關注的解決方案涉及更大的藍圖:咱們須要在規劃和設計系統方面作得更好。redux
想象一個你認爲設計優雅的產品。一種可以抵禦住用戶大量隨機交互後果的東西————你懂的,當用戶按下按鈕的次數超過預期時,這種不可預測性會以某種意想不到的順序與輸入來進行交互,或者是可以以某種其它方式來讓你懷疑人生。生活老是很難在正軌上。瀏覽器
讓我來預測下你正在想象的這個產品是什麼吧。
嗯……你可能不會考慮專爲web而打造的東西,那裏的哲學彷佛是"快速行動,破除陳規"【譯者注:原文是:'move fast and break things'.這句話被做爲標語貼在Facebook的辦公室內】。
從更新頻率上來看,你可能也沒有考慮過手機。
你可能甚至都沒想過那些最近纔開發的東西。咱們在開發可靠產品方面的能力彷佛在倒退。
我想我知道你在想什麼了…
我猜對了嗎?...也許沒有?【譯者注:確定是沒有...原po多是個索粉...】
你可能不知道,這是20世紀80年代生產的一款的索尼隨身聽【walkman】。
小時候,我從一位已經升級到使用便攜CD播放器的朋友那裏收到了一個相似wlakman的卡帶播放器。我明白,一些年輕的讀者可能對這兩種設備都不太熟悉。大家能夠把Walkman想象成iPhone,但按鍵更大,也更容易壞。個人主要任務就是:搞壞它。
我將會嘗試全部不一樣按鈕的使用組合,看看會發生什麼:
在盡我所能的各類嘗試下,索尼Walkman的表現依舊比今天絕大多數網站都要好。
像Walkman這樣的電子設備經受住了用戶測試的挑戰,沒有任何隱藏或禁用用戶界面元素的能力。任何按鈕均可以隨時被按下,任何事情均可能發生。然而它彷佛依舊牢不可破。
這讓我想知道:
也許電子設備能爲咱們如何構建網絡界面提供了更好的範例。
咱們能夠從古老的電子產品設計過程當中學到什麼?咱們如何更好地設計應用?馬蒂,咱們須要回到將來!【譯者注:出自美國經典科幻電影,《回到將來》.Marty是影片中的男主角.】
電子產品真的能教咱們如何更好地在瀏覽器中建立應用嗎?
考慮到組件在過去五年中是Web開發中最大的變化之一。也許咱們能夠借用一些電子工程中的其餘概念嗎?
做爲Web開發人員,咱們的條件已經很好了。真的真的很好。發現了一個bug?不要緊,咱們能夠在一小時內將更新部署到服務器。
可是其餘的工程領域並非那麼寬容的。硬件產生問題每每直接致使設備被廢棄。嵌入式開發人員必須很是當心,以確保固件更新時不會耗盡電池或者讓全部現有設備崩潰。
Web開發者擁有有一種不計後果的奢侈。
更不用說,軟件開發者不多會遇到和電子設備開發者同樣的資源限制。你最近一次關心的多是性能和內存使用,而不是如何讓這該死的東西工做。每秒60幀是一道低門檻。但隨着咱們開始構建愈來愈複雜的應用且但願它能在低端手機和物聯網設備上運行,這道門檻的標準正在上升。咱們正逐漸面臨底層工程師數十年來所經歷的工程問題。
約束培育創造力。限制帶來了更好的設計。
要了解如何限制會帶來更好的設計,咱們須要回到狀態管理的基礎。
當下社區中的討論方向每每是傾向NPM包而不是基本的計算機科學原理。
工程師們歷來不會問:「哪一個庫更好?」他們會問:「咱們該如何設計一個更好的系統?」
咱們能夠從一些設計良好的基本原則開始:
我將經過8個要點來使用本身的方式完成這些任務。
在程序系統中,狀態和數據之間的差別是模糊的。他們都保存在內存中,所以被相同對待。
在React中,狀態和數據共享相同的名字和機制:
this.state
this.state = {}
this.setState(nextState)
在電子產品中,對狀態和數據的區別就沒有那麼多的混淆。
狀態表示系統能夠處於有限數量的模式下————一般由電路自己定義。對Walkman來講,就像是「Playing」,「Stopped」,「Ejected」。就像是一種「模式」或「配置」,狀態是可數的。
數據存儲在具備幾乎有無限可能的存儲器中。對Walkman來講,就像是正在演奏的歌曲「Song 2」。數據,就像歌曲,擁有無限的可能性。
不管下面的DataLoader
組件作什麼,它的狀態都只可能生成一組有限的視圖:「loading」,「loaded」,「error」。
分離狀態和數據能夠減小混淆,並容許咱們構建基於有限狀態機的應用。
電子產品開發者很早就知道,一個可預測的界面必定是具備有限和可控狀態數量的狀態。若是狀態的數量不加控制,系統不只會難以調試,更沒法進行完全的測試。
在有限狀態機中,狀態是被明肯定義的。轉換是一組你能夠進行狀態轉移事件的集合。
舉個栗子,使用事件「STOP」會觸發狀態轉換將狀態轉移至「Stopped」。
在React中,咱們能夠定義一個擁有兩種狀態的基本款Walkman:「Stopped」和「Playing」。
能夠在這個CodeSandBox中查看具體演示。
在一個有限狀態機中,系統始終會處於一個可能的配置下。這個界面絕無可能出現除了「Playing」和「Stopped」以外的東西。只要對這兩種狀態進行測試就可以讓咱們對系統充滿信心!
讓咱們看看當咱們開始向這個狀態機樣例中添加兩個新狀態時會發生什麼:「Rewinding」和「FastForwarding」。
當狀態相同時,他們看起來是很容易添加的。每個狀態都像是一個能夠單獨單獨開發和測試的模塊。不過要當心,狀態轉換並不老是可行的。
咱們應該注意不一樣狀態間不受控制的轉換
也許被你發現了。咱們在上面的代碼裏引入了一個bug。花點時間去看看你可否發現其中的問題。
因爲咱們容許用戶在rewinding
和fastForwarding
兩種狀態間快速切換,並且沒有在切換過程當中去將磁帶暫停。咱們的磁帶彷佛纏成了一坨。
爲了解決這個問題,咱們能夠爲咱們的狀態轉換添加一層守衛。守衛是狀態轉換髮生所必需要知足的條件。例如:咱們能夠確保事件FASTFORWARD
,REWIND
,和PLAY
只能在狀態爲「Stopped」時觸發。
除非咱們從新思考咱們狀態管理的規劃和設計方式,不然意外的狀態轉換是必然發生的。
當咱們添加諸如ejected
這樣的額外狀態時,咱們必須去思考哪一種狀態轉換在哪一種條件下是被容許的。拿Walkman來講,你能夠在它處於中止模式時按下中止鍵來彈出磁帶。爲了添加這個功能,咱們必須添加更多的守衛,並肯定哪些轉換是可行的。
伴隨着各類新狀態的添加,各類未處理狀態組合的可能性成倍的增長。這並非一個可拓展的解決方案。每一個額外狀態都會檢查全部的轉換守衛。
這開始變得像是狀態在管理你了。
管理守衛的問題來源於狀態的表現方式:「Stopped」,「Playing」,「Rewinding」。
狀態的理想數據結構並非字符串或對象。
但那又會是什麼呢?
狀態理想的數據結構一般是圖。狀態圖提供了一種直觀的方法來設計,可視化的控制狀態在每一個節點間的轉換。
這並非什麼大新聞————電子工程師們這幾十年來一直在用狀態圖來描述複雜系統。
咱們來看一個網上的例子。AWS Step Function爲繪製應用程序工做流提供了可視化界面。每一個節點控制一個lambda————一個在雲上調用的遠程函數————每一個函數的輸出會觸發下一個函數的輸入。
在上面的例子中,咱們能夠很清楚的看到用戶的操做是如何在每一個步驟中移動的。包括那些可能的錯誤以及錯誤處理。添加額外的步驟並不會致使複雜性的指數級增長。
一些工程師可能已經注意到了,Step Function與PLC(可編程邏輯控制器)模塊接線圖有很多共同之處。一些設計師可能會說這與他們的工做流程圖也有不少共同點。咱們設計狀態的方式難道不該該與設計應用的方式有更多的共同之處嗎?
狀態圖應當成爲開發應用的腳手架。
舉個例子。經過狀態圖,咱們能夠更加容易直觀的理解Walkman的操做。
若是沒有深刻研究一些較爲複雜的「守衛」代碼,咱們確定會說除非在「Stopped」狀態下,不然都不該該從「Rewinding」跳到任何其它狀態。與此相反的是,你應該列出你的應用能夠作的全部轉換,而不是概述的說它不能作什麼事。開發方式從防護性的自下向上的編碼轉爲自頂向下的設計。這種轉換是事半功倍的。
狀態圖更直觀,更容易調試,並且更容易容納新需求。經過狀態機,每種狀態的變化都可以與它相鄰的狀態相隔離。更不用說那些複雜的狀態「守衛」邏輯能夠經過一種更加直觀易理解的方式去涵蓋。
不幸的是,狀態圖也多是一顆定時炸彈
重度連通圖是無法縮放的。想一想若是咱們在上圖中再添加另外4個狀態會發生什麼。可讀性下降並且重複性增長,各個糾纏的箭頭指向各個方向去爭奪空間。這種狀態圖的泛化被稱爲狀態爆炸。
幸運的是,有一種使用形式化描述系統的方式能夠用來減輕狀態圖的複雜度:讓咱們開始探索statecharts。
我第一次瞭解到statecharts是在Luca Matteis在溫哥華React直面會上所作的《如何使用statecharts爲Redux應用的行爲建模》演講。次日在工做中,我提出了「新」的狀態管理模式,其實我只是發現了許多我身邊的工程師早已熟悉的概念。我在一家基於IOT的公司工做,與許多硬件和嵌入式開發的工程師一塊兒工做。咱們正在招聘:)。
statechart的概念能夠追溯到1987年,當時數學家David Harel發表了一篇關於可視化描述複雜系統的論文。就如下面的石英錶爲例:
一旦你理解了它自身的語言,statechart既直觀又容易掌握。
上面的例子介紹了一些新的狀態類型:
除此以外,statecharts還能夠包含觸發轉換和行爲的時間和方式:
onEntry
或onExit
上。將Walkman示例用statechart重寫能夠刪除狀態圖中冗餘的部分。注意,如今再也不須要重複「STOP」事件了。statechart是可擴展的————添加其餘並行狀態(如「Recording」和「Volume」)並不難。
Statechart並不只僅是一個可視化描述應用程序的概念。
statechart能夠爲咱們生成應用程序底層的狀態機
你能夠將看到的圖轉化爲代碼,反之亦然。以圖表的形式來檢視你的應用邏輯,或者把它畫出來。
Statecharts爲真正設計系統提供了一個充滿但願的將來————而不只僅是紙上談兵。雖然其餘編程語言已經出現了相關工具,但JavaScript如今纔剛剛開始顯現出statechart工具的繁榮。
C和Java的開發人員能夠經過使用工具來編寫statechart進行編碼。做爲一個例子,Yakindu Statechart Tools中聚集了各類可視化設計和代碼。我最近學會了Yakindu還作了一個Typescript代碼生成器。
一樣的工具最終也能夠用於JavaScript。
Sketch Systems提供了一種在markdown中設計系統的方法。這能夠用於在JavaScript中設計原型。雖然Sketch Systems尚不支持行爲或守衛,但我發現它對於原型設計和測試狀態圖都很是有用。
Sketch Systems容許你將圖表導出到XState,這是一個基於statechart的JavaScript庫,具備本身的可視化和可點擊狀態原型設計工具。
想象一下你編輯器中更高級的工具。想象一下你的工做流程,你能夠在可視化設計和手動編寫應用程序邏輯之間無縫切換。在社區中來推進咱們想要更好地支持使用statechart的工具,庫和編輯器插件,咱們所必須付出的巨大努力是值得的。
複雜度已經悄然降臨到了JavaScript社區中了。我認爲咱們尚未作好準備。就我的而言,我認可我花了很長時間纔開始擅長設計應用。我會先畫出一個組件樹和一些狀態的草稿。看着原型圖一步步迭代進生產。可是,若是沒有一個可視化規格語言來設計狀態圖,我怎能真正擅長設計應用程序呢?
在很長一段時間裏,我坦白我接近狀態管理更多的是僅僅是好奇它的高深莫測。我不知道從計算機科學的其餘領域也能夠學到不少東西,有更長的創建和管理複雜系統的歷史。我逐漸明白,回顧過去是有價值的,而且要多關注咱們周圍的工程領域。
咱們能夠向那些開創並開發了數十年系統解決方案的工程師學習,這些解決方案可用於建立複雜但可預測的系統。咱們能夠在工具和庫的基礎上構建一個生態系統,以支持應用程序邏輯的可視化設計。咱們須要這麼作,由於JavaScript須要有這些東西。
在JavaScript中設計應用程序擁有比以往都更加光明的將來。這篇文章站在很是高的角度上,可能留下的問題多於答案。在第2部分中,我想更深刻地討論使用statecharts構建組件的模式。