MobX
是一個簡單、可擴展且經過實戰測試的狀態管理解決方案。本教學將在十分鐘內引導您了解 MobX 的所有重要概念。MobX 是一個獨立的函式庫,但大多數人將它與 React 搭配使用,本教學也將著重於此組合。
核心概念
狀態是每個應用程式的核心,而產生不一致的狀態或與殘留的局部變數不同步的狀態,是導致應用程式錯誤百出且難以管理的最快途徑。因此,許多狀態管理解決方案試圖限制修改狀態的方式,例如使狀態不可變。但這引入了新的問題;資料需要正規化,參照完整性無法再得到保證,而且如果您喜歡使用類別等強大的概念,這幾乎變得不可能。
MobX 透過解決根本問題,讓狀態管理再次變得簡單:它讓產生不一致的狀態變得不可能。實現這一目標的策略很簡單:*確保所有可以從應用程式狀態衍生的內容都能被衍生。自動地。*
從概念上講,MobX 將您的應用程式視為一個試算表。
- 首先,有 *應用程式狀態*。物件、陣列、基本類型、參照的圖表構成了您的應用程式模型。這些值是您應用程式的「資料儲存格」。
- 其次是 *衍生*。基本上,任何可以從應用程式狀態自動計算的值。這些衍生值或計算值可以從簡單的值(例如未完成待辦事項的數量)到複雜的東西(例如待辦事項的視覺化 HTML 表示)。用試算表的術語來說:這些是您應用程式的公式和圖表。
- *反應* 與衍生非常相似。主要區別在於這些函式不會產生值。相反,它們會自動運行以執行某些任務。通常這與 I/O 相關。它們確保 DOM 得到更新,或者網路請求在正確的時間自動發出。
- 最後是 *動作*。動作是所有改變 *狀態* 的事物。MobX 將確保您的動作導致的應用程式狀態的所有更改都由所有衍生和反應自動處理。同步且無故障。
一個簡單的待辦事項儲存庫...
理論夠了,實際操作可能比仔細閱讀上述內容更具說明性。為了獨創性,讓我們從一個非常簡單的待辦事項儲存庫開始。請注意,以下所有程式碼區塊都是可編輯的,因此請使用 *執行程式碼* 按鈕來執行它們。以下是一個非常簡單的 TodoStore
,它維護一個待辦事項集合。目前還沒有用到 MobX。
我們剛建立了一個帶有 todos
集合的 todoStore
實例。是時候用一些物件填充 todoStore 了。為了確保我們看到更改的效果,我們在每次更改後調用 todoStore.report
並記錄它。請注意,該報告故意始終僅列印 *第一個* 任務。這使得這個例子有點不自然,但正如我們稍後將看到的,它很好地證明了 MobX 的依賴性追蹤是動態的。
變得具有反應性
到目前為止,這段程式碼沒有什麼特別之處。但是,如果我們不必明確地調用 report
,而是可以宣告它應該在每次 *相關* 狀態更改時被調用呢?這將使我們不必負責從程式碼庫中任何 *可能* 影響報告的位置調用 report
。我們確實希望確保列印最新的報告。但我們不想被組織它所困擾。
幸運的是,這正是 MobX 可以為我們做的事情。自動執行僅依賴於狀態的程式碼。這樣我們的 report
函式就會自動更新,就像試算表中的圖表一樣。為了實現這一點,TodoStore
必須變得可觀察,以便 MobX 可以追蹤所有正在進行的更改。讓我們稍微修改一下類別以實現這一點。
此外,completedTodosCount
屬性可以從待辦事項列表中自動衍生。透過使用 observable
和 computed
標記,我們可以在物件上引入可觀察的屬性。在下面的例子中,我們使用 makeObservable
來明確顯示標記,但我們也可以使用 makeAutoObservable(this)
來簡化這個過程。
就這樣!我們將一些屬性標記為 observable
,以向 MobX 發出信號,表明這些值會隨著時間而改變。計算用 computed
裝飾,以表明這些可以從狀態和快取中衍生,只要底層狀態沒有改變。
pendingRequests
和 assignee
屬性目前尚未使用,但將在本教學的後面部分使用。
在建構函式中,我們建立了一個列印 report
的小函式,並將其包裝在 autorun
中。Autorun 建立一個 *反應*,它運行一次,然後在函式內使用的任何可觀察資料發生更改時自動重新運行。因為 report
使用可觀察的 todos
屬性,所以它會在適當的時候列印報告。這在下一個清單中得到了證明。只需按下 *執行* 按鈕
很有趣,對吧?report
的確自動、同步且沒有洩漏中間值地列印了。如果您仔細研究日誌,您會發現第五行沒有產生新的日誌行。因為報告並沒有因為重新命名而 *實際* 發生變化,儘管備份資料確實發生了變化。另一方面,更改第一個待辦事項的名稱確實更新了報告,因為該名稱在報告中被積極使用。這很好地證明了不僅 todos
陣列被 autorun
觀察,而且待辦事項中的各個屬性也被觀察。
使 React 具有反應性
好的,到目前為止,我們製作了一個愚蠢的具有反應性的報告。是時候圍繞這個相同的儲存庫構建一個具有反應性的使用者介面了。React 元件(儘管它們的名稱)並不是開箱即用的反應性。mobx-react-lite
套件中的 observer
高階元件包裝器透過基本上將 React 元件包裝在 autorun
中來解決這個問題。這使元件與狀態保持同步。這在概念上與我們之前對 report
所做的沒有什麼不同。
下一個清單定義了一些 React 元件。其中唯一特定於 MobX 的程式碼是 observer
包裝器。這足以確保每個元件在相關資料更改時單獨重新渲染。我們不必再調用狀態 useState
設定器,也不必弄清楚如何使用需要配置的選擇器或高階元件訂閱應用程式狀態的適當部分。基本上,所有元件都變得聰明了。然而,它們是以一種愚蠢的、宣告性的方式定義的。
按下 *執行程式碼* 按鈕以查看以下程式碼的實際效果。該清單是可編輯的,因此您可以隨意使用它。例如,嘗試刪除所有 observer
調用,或者只刪除裝飾 TodoView
的那個。右側預覽中的數字突出顯示了元件渲染的頻率。
下一個清單很好地證明了我們只需要更改資料,而無需進行任何進一步的簿記。MobX 將再次從儲存庫中的狀態自動衍生和更新使用者介面的相關部分。
使用參照
到目前為止,我們已經建立了可觀察的物件(原型物件和普通物件)、陣列和基本類型。您可能想知道,MobX 中是如何處理參照的?我的狀態允許形成圖表嗎?在前面的清單中,您可能已經注意到待辦事項上有一個 assignee
屬性。讓我們透過引入另一個包含人員的「儲存庫」(好吧,它只是一個美化的陣列)並將任務分配給他們來賦予他們一些價值。
我們現在有兩個獨立的儲存庫。一個用於人員,一個用於待辦事項。要將 assignee
分配給人員儲存庫中的人員,我們只需分配一個參照。這些更改將被 TodoView
自動拾取。使用 MobX,無需先正規化資料,也無需編寫選擇器來確保我們的元件將被更新。事實上,資料儲存在哪裡都無所謂。只要物件是 *可觀察的*,MobX 就能夠追蹤它們。真正的 JavaScript 參照就可以工作。如果它們與衍生相關,MobX 將自動追蹤它們。要測試這一點,只需嘗試在下面的輸入框中更改您的姓名(確保您事先按下了上面的 *執行程式碼* 按鈕!)。
您的姓名
順便說一句,上面輸入框的 HTML 程式碼很簡單
<input onkeyup="peopleStore[1].name = event.target.value" />
非同步動作
由於我們小型待辦事項應用程式中的所有內容都是從狀態衍生出來的,因此狀態 *何時* 更改實際上並不重要。這使得建立非同步動作非常簡單。只需按下以下按鈕(多次)即可模擬非同步載入新的待辦事項
其背後的程式碼非常簡單。我們首先更新儲存庫屬性 pendingRequests
以反映目前的載入狀態。載入完成後,我們更新儲存庫的待辦事項並再次減少 pendingRequests
計數器。只需將此程式碼片段與之前的 TodoList
定義進行比較,即可了解 pendingRequests
屬性是如何使用的。
請注意,timeout 函式包裝在 action
中。這並非嚴格必要,但它確保兩個變異都在單個事務中處理,確保任何觀察者僅在兩個更新都完成後才收到通知。
observableTodoStore.pendingRequests++; setTimeout(action(() => { observableTodoStore.addTodo('Random Todo ' + Math.random()); observableTodoStore.pendingRequests--; }), 2000);
結論
就這樣!沒有樣板程式碼。只是一些簡單的、宣告性的元件構成了我們完整的 UI。並且它們完全是從我們的狀態中反應性地衍生出來的。您現在可以在您自己的應用程式中開始使用 mobx
和 mobx-react-lite
套件了。以下是您目前所學內容的簡要總結
- 使用
observable
裝飾器或observable(物件或陣列)
函式使物件可供 MobX 追蹤。 computed
裝飾器可用於建立可以自動從狀態衍生值並快取它們的函式。- 使用
autorun
自動運行依賴於某些可觀察狀態的函式。這對於記錄、發出網路請求等很有用。 - 使用
mobx-react-lite
套件中的observer
包裝器使您的 React 元件真正具有反應性。它們將自動且有效地更新。即使在具有大量資料的大型複雜應用程式中使用也是如此。
您可以繼續使用上面可編輯的程式碼區塊,以初步了解 MobX 如何對您的所有更改做出反應。例如,您可以在 report
函式中新增一個日誌語句,以查看它何時被調用。或者根本不顯示 report
,看看這如何影響 TodoList
的渲染。或者只在特定情況下顯示它...
MobX 不會強制規定架構
請注意,以上範例僅為 contrived examples,建議使用正確的工程實務,例如將邏輯封裝在方法中,將它們組織在 stores、controllers 或 view-models 等中。可以應用許多不同的架構模式,官方文件中會進一步討論其中一些。以上範例以及官方文件中的範例展示了 MobX *可以* 如何使用,而不是它 *必須* 如何使用。或者,正如 HackerNews 上的某個人所說
「MobX,它在其他地方被提及過,但我忍不住要讚揚它。使用 MobX 意味著使用 controllers/ dispatchers/ actions/ supervisors 或其他形式的資料流管理,回歸到可以根據應用程式需求調整模式的架構考量,而不是對於任何比 Todo 應用程式更複雜的東西都預設需要的東西。」