作者:安傑洛·馬丁斯
現代web應用程序的狀態管理是困難的。Redux是最適合這項工作的庫之一,但它並不完美。
在這篇文章中,我解釋了我們在Redux中遇到的問題,以及為什麼我們決定建立自己的狀態管理庫,而不是使用其他流行的庫,如MobX, Recoil或RXJs。
問題1:性能
Redux有一個中央商店,你的應用程序的所有狀態信息都應該在裏麵。
中央存儲有好處,例如,很容易看到當前的所有狀態,因為所有東西都在同一個地方。這就是redux如此受歡迎的原因。
但是如果你有一個大型的應用程序,它會隨著時間的推移而增長,Redux減量器、選擇器和效果的數量將會急劇增長。
每個動作都會經過所有的減量器和效果,雖然它們的目的是快速和跳過他們不關心的動作,但它仍然需要一些計算。你擁有的越多,速度就越慢。
我們還可以在應用程序中同時擁有數百個“選擇器”。每次對應用程序狀態的更新都會觸發選擇器鏈,重新計算它們的值,並迫使react組件重新呈現顯示新數據。它會使您的應用程序性能很差。
可以優化選擇器以緩存最新的值,但這很難實現,而且很容易錯過。
redux本身還有兩個難以解決的性能問題:大數據和頻繁更新。
您可以擁有如此多的數據,以至於無法將其存儲在狀態中。在我們的例子中,它從具有數百萬行的表開始。
您可以在短時間內擁有頻繁更新的數據,並結合大量更新一小部分狀態的操作,將數據放入像redux這樣的大型中央存儲中是沒有意義的。
為了解決這些問題,我們將這些片段從redux移到包含訂閱更改功能的類中。
問題2:冗長
使用redux,有很多樣板代碼來完成簡單的任務。我們有很多帶有動作、減少器、效果和選擇器的文件。業務邏輯分布在多個文件中,以便將其放入Redux。
問題3:異步代碼
redux沒有異步代碼。當“異步”操作開始和結束時,您需要創建效果來分派操作。
如果你需要在服務器上保存一些數據,你需要一個動作來觸發“保存”效果,另一個動作來發送一個動作來更新狀態為“保存”,然後你發送一個結果動作。
如果您有其他一些代碼想要調用save函數並等待操作完成,僅使用Redux,這是不可能的。代碼必須訂閱在處理異步操作時分派的動作。
最大的挑戰
如何將處於Redux狀態的數據與非Redux狀態的數據結合起來?
例如,我們如何處理一個插入/刪除按鈕,該按鈕需要根據redux狀態可見/啟用,同時還要監視選定的單元格和行數據(由於大小原因,我們將它們排除在redux狀態之外),以確保它們是可編輯的?
為了實現這一點,我們不得不尋找其他選擇,我們需要一種方法來觀看和組合多個訂閱。
為什麼不是其他庫?
反衝
就第三方庫而言,《Recoil》是最佳選擇,我們受到了它的啟發。Facebook創建Recoil是為了解決Redux的問題。
然而,《Recoil》並不適合我們,原因如下:
- 它與React綁定,你隻能在React應用程序中使用Recoil。我們希望在使用哪個前端框架構建應用程序方麵保持靈活性。
- 它需要< RecoilRoot >在react樹的頂部,但是我們想要一些沒有這個要求的東西,這樣開發人員就可以創建組件並將它們放在任何應用程序的任何地方。
- 我們認為選擇器的“get”函數樣式不太直觀(我們更喜歡Redux中的重新選擇樣式)。
- 每個狀態塊(原子)都需要一個唯一的字符串鍵,並且需要注意不要重複鍵。
MobX
MobX看起來也是個不錯的選擇:
- MobX使用代理來包裝對象和檢測到的突變。當您使用MobX查看組件時,很難確定哪些對象正在被監視,以及是什麼觸發了更改。
- MobX迫使你使用麵向對象的編程風格。對象中的任何屬性都可以被改變。添加自定義相等函數來檢查狀態是否已更改並不容易(可以讓不同的新數組和對象實例看起來相同)。
在Kinaxis,我們混合了函數式編程和麵向對象編程,從麵向對象編程中我們使用了一些沒有繼承的類,因為有時類更快更幹淨。我們專注於函數式編程方麵,比如不變性、組合、純函數。專注於OOP並不是一個選擇。
RxJs
我們發現RxJs複雜而沉重。我們以前使用過rxj,並沒有很好的體驗。代碼可能會變得非常複雜,難以遵循和調試。對於初級開發人員來說,這可能非常令人生畏。觀察對象所需的一些功能鏈並不直觀。
解決方案
不,解決辦法不是膠帶!我們需要一個合適的解決方案;一個能解決我們所有問題的圖書館。
它有這些要求:
- 快
- 可伸縮的
- 框架不可知:它應該能夠與任何基於Javascript的應用程序框架(React, Angular, Vue -服務器端或客戶端)集成。
- 去中心化狀態:一個昂貴的狀態不應該幹擾整個應用程序的性能。
- 它應該能夠創建可以組合不同的狀態和其他選擇器的選擇器。
- 訂閱應該由框架處理,以便在需要時自動訂閱和取消訂閱。
- 它需要易於使用、理解和調試。
- 開發人員應該完全控製所發生的事情。
- 它應該支持異步操作。
- 它應該批量更新,以便在下一個js周期更新一次UI。
很難滿足所有這些要求,但我們做到了!
在最初的實現中,我簡直不敢相信它是如此簡單!
警告
我們新的狀態管理庫還不是開源的。我希望它能在2022年開源,因為它正在變得成熟,經過戰鬥測試,被證明是可擴展的,快速的,易於使用的。
以下是它的工作原理:
Api
這個庫有兩個包:
- 一個包含純typescript代碼的核心包,它有兩個主要功能:createValue而且createValueSelector。
- 的反應包useValue而且useAsyncValue鉤子。
createValue
的createValue函數接受以下參數:
- initialValue-要包裝的值。
- equalityFn-(可選)檢查值是否已更改以觸發訂閱者的功能。
它返回一個對象:
- getValue ()-函數獲取當前值;
- setValue (newValue)-設置新值。
- 訂閱(聽眾)> -添加一個回調函數,當值發生變化時調用退訂函數。
createValueSelector
的createValueSelector函數接受以下參數:
- 值-用於觀察變化的值數組。
- 合路器-一個函數,接受所有當前值並返回一個新值,它的靈感來自redux的重選庫。
- equalityFn-(可選)檢查值是否已更改以觸發訂閱者的功能。
它返回一個對象:
- getValue ()-函數獲取當前值;
- 訂閱(聽眾)-添加一個回調函數,當值發生變化時調用退訂函數。
請注意
createValueSelector沒有setValue函數的內部值是私有的,總是派生的合路器函數傳遞給它。
useValue
的useValueHook可以在react組件中使用,用於自動訂閱值,並在組件從頁麵中移除時自動取消訂閱。
useAsyncValue
就像useValue鉤子一樣,useAsyncValuehook可以在react組件中使用,自動訂閱“Promise”值,並在組件從頁麵中刪除時取消訂閱。鉤子公開承諾的狀態以及解析時從該值派生的結果或錯誤。
提示
這還不是全部!這隻是對我們的新狀態管理庫的一個快速概述。它有許多其他的功能和工具來調試內存泄漏,檢測不必要的更新,並提示如何解決它們。隨著圖書館的發展,將會有更多令人興奮的發展。
最終的想法
Redux很好,但是沒有一個放之四海皆準的庫。評估這些選擇,找出最適合你和你的團隊的方案。
我希望您能理解為什麼我們不得不放棄redux,以及為什麼我們決定構建自己的狀態管理庫。
作者注:
Angelo Martins是安大略省渥太華的高級軟件開發人員,也是Kinaxis的前首席軟件開發人員。
留下回複