MobX
簡單、可擴展的狀態管理
MobX 是由 Mendix、Coinbase、Facebook 開源和眾多個人贊助商所贊助的。
安裝
- 安裝:
npm install mobx --save
。 React 綁定庫:npm install mobx-react --save
。 要啟用 ESNext 的裝飾器 (可選), 參見下面。 - CDN:
瀏覽器支持
- MobX >=5 版本運行在任何支持 ES6 proxy 的瀏覽器。如果運行在像 IE11、Node.js 6 以下版本或依靠與較舊的 JavaScripCore 的安卓端的 React Native (點擊查看如何升級])。
- MobX 4 可以運行在任何支持 ES5 的瀏覽器上,而且也將進行持續地維護。MobX 4 和 5 的 API 是相同的,並且語義上也能達到相同的效果,只是 MobX 4 存在一些 局限性。
小貼士: MobX 5 包的主入口點附帶 ES5 代碼,以便向后兼容所有構建工具。但因為 MobX 5 只能運行在現代瀏覽器上,所以可以考慮使用速度最快、體積最小的 ES6 構建: lib/mobx.es6.js
。例如,通過設置 webpack 的別名: resolve: { alias: { mobx: __dirname + "/node_modules/mobx/lib/mobx.es6.js" }}
入門指南
- egghead.io 課程
- 十分鍾交互式的 MobX + React 教程
由 Pavan Podila 和 Michel Weststrate 撰寫的 MobX 書籍 (非常不喜歡xx深入的書名!)
- MobX 4官方文檔和API概覽 (MobX 3, MobX 2)
- 視頻:
- ReactNext 2016: 真實世界的 MobX - 40分鍾 幻燈片
- React 和 MobX 實戰. OpenSourceNorth 開發者大會上,Matt Ruby 深入介紹和說明如何使用MobX和React(ES5版本) - 42分鍾
- LearnCode.academy MobX 教程 第一部分: MobX + React 太棒了 (7分鍾) 第二部分: Computed Values and 嵌套/引用的 Observables (12分鍾)
- 錄播: MobX 介紹 - 8分鍾
- 訪談: 狀態管理很容易 - React Amsterdam 2016 開發者大會 (幻燈片)
- 樣板文件和相關項目
- 更多教程、博客和視頻盡在 MobX 主頁
- 更多教程、博客、視頻和其他有用的資源盡在 Awesome MobX
入門
MobX 是一個經過戰火洗禮的庫,它通過透明的函數響應式編程(transparently applying functional reactive programming - TFRP)使得狀態管理變得簡單和可擴展。MobX背后的哲學很簡單:
任何源自應用狀態的東西都應該自動地獲得。
其中包括UI、數據序列化、服務器通訊,等等。
React 和 MobX 是一對強力組合。React 通過提供機制把應用狀態轉換為可渲染組件樹並對其進行渲染。而MobX提供機制來存儲和更新應用狀態供 React 使用。
對於應用開發中的常見問題,React 和 MobX 都提供了最優和獨特的解決方案。React 提供了優化UI渲染的機制, 這種機制就是通過使用虛擬DOM來減少昂貴的DOM變化的數量。MobX 提供了優化應用狀態與 React 組件同步的機制,這種機制就是使用響應式虛擬依賴狀態圖表,它只有在真正需要的時候才更新並且永遠保持是最新的。
核心概念
MobX 的核心概念不多。 下面的代碼片段可以在 codesandbox 示例中在線試用。
Observable state(可觀察的狀態)
Egghead.io 第1課: observable & observer
MobX 為現有的數據結構(如對象,數組和類實例)添加了可觀察的功能。 通過使用 @observable 裝飾器(ES.Next)來給你的類屬性添加注解就可以簡單地完成這一切。
1 import { observable } from "mobx"; 2 3 class Todo { 4 id = Math.random(); 5 @observable title = ""; 6 @observable finished = false; 7 }
使用 observable
很像把對象的屬性變成excel的單元格。 但和單元格不同的是,這些值不只是原始值,還可以是引用值,比如對象和數組。
如果你的環境不支持裝飾器語法,也不必擔心。 你可以點擊這里查看如何進行設置。 或者你可以直接跳過設置,因為 MobX 可以通過 decorate 工具在不支持裝飾器語法的情況加使用。 盡管如此,多數 MobX 用戶更喜歡裝飾器語法,因為它更簡潔。
例如,上面一段代碼的ES5版本應該是這樣:
1 import { decorate, observable } from "mobx"; 2
3 class Todo { 4 id = Math.random(); 5 title = ""; 6 finished = false; 7 } 8 decorate(Todo, { 9 title: observable, 10 finished: observable 11 })
Computed values(計算值)
使用 MobX, 你可以定義在相關數據發生變化時自動更新的值。 通過@computed
裝飾器或者利用 (extend)Observable
時調用 的getter / setter 函數來進行使用。(當然,這里也可以再次使用 decorate
來替代 @
語法)。
1 class TodoList { 2 @observable todos = []; 3 @computed get unfinishedTodoCount() { 4 return this.todos.filter(todo => !todo.finished).length; 5 } 6 }
當添加了一個新的todo或者某個todo的 finished
屬性發生變化時,MobX 會確保 unfinishedTodoCount
自動更新。 像這樣的計算可以類似於 MS Excel 這樣電子表格程序中的公式。每當只有在需要它們的時候,它們才會自動更新。
Reactions(反應)
Reactions 和計算值很像,但它不是產生一個新的值,而是會產生一些副作用,比如打印到控制台、網絡請求、遞增地更新 React 組件樹以修補DOM、等等。 簡而言之,reactions 在 響應式編程和命令式編程之間建立溝通的橋梁。
React 組件
Egghead.io 第1課: observable & observer
如果你用 React 的話,可以把你的(無狀態函數)組件變成響應式組件,方法是在組件上添加 observer
函數/ 裝飾器. observer
由 mobx-react
包提供的。
1 import React, {Component} from 'react'; 2 import ReactDOM from 'react-dom'; 3 import {observer} from 'mobx-react'; 4
5 @observer 6 class TodoListView extends Component { 7 render() { 8 return <div>
9 <ul>
10 {this.props.todoList.todos.map(todo =>
11 <TodoView todo={todo} key={todo.id} />
12 )} 13 </ul>
14 Tasks left: {this.props.todoList.unfinishedTodoCount} 15 </div>
16 } 17 } 18
19 const TodoView = observer(({todo}) =>
20 <li>
21 <input 22 type="checkbox"
23 checked={todo.finished} 24 onClick={() => todo.finished = !todo.finished} 25 />{todo.title} 26 </li>
27 ) 28
29 const store = new TodoList(); 30 ReactDOM.render(<TodoListView todoList={store} />, document.getElementById('mount'));
observer
會將 React (函數)組件轉換為它們需要渲染的數據的衍生。 使用 MobX 時沒有所謂的智能和無腦組件。 所有的組件都會以巧妙的方式進行渲染,而只需要一種簡單無腦的方式來定義它們。MobX 會確保組件總是在需要的時重新渲染,但僅此而已。所以上面例子中的 onClick
處理方法會強制對應的 TodoView
進行渲染,如果未完成任務的數量(unfinishedTodoCount)已經改變,它將導致 TodoListView
進行渲染。 可是,如果移除 Tasks left
這行代碼(或者將它放到另一個組件中),當點擊 checkbox
的時候 TodoListView
就不再重新渲染。你可以在 JSFiddle 中自己動手來驗證這點。
自定義 reactions
使用autorun
、reaction
和 when
函數即可簡單的創建自定義 reactions,以滿足你的具體場景。
例如,每當 unfinishedTodoCount
的數量發生變化時,下面的 autorun
會打印日志消息:
autorun(() => {
console.log("Tasks left: " + todos.unfinishedTodoCount) })
MobX 會對什么作出響應?
為什么每次 unfinishedTodoCount
變化時都會打印一條新消息?答案就是下面這條經驗法則:
MobX 會對在執行跟蹤函數期間讀取的任何現有的可觀察屬性做出反應。
想深入了解 MobX 是如何知道需要對哪個可觀察屬性進行響應,請查閱 理解 MobX 對什么有反應。
Actions(動作)
不同於 flux 系的一些框架,MobX 對於如何處理用戶事件是完全開明的。
- 可以用類似 Flux 的方式完成
- 或者使用 RxJS 來處理事件
- 或者用最直觀、最簡單的方式來處理事件,正如上面演示所用的
onClick
最后全部歸納為: 狀態應該以某種方式來更新。
當狀態更新后,MobX
會以一種高效且無障礙的方式處理好剩下的事情。像下面如此簡單的語句,已經足夠用來自動更新用戶界面了。
從技術上層面來講,並不需要觸發事件、調用分派程序或者類似的工作。歸根究底 React 組件只是狀態的華麗展示,而狀態的衍生由 MobX 來管理。
1 store.todos.push( 2 new Todo("Get Coffee"), 3 new Todo("Write simpler code") 4 ); 5 store.todos[0].finished = true;
盡管如此,MobX 還是提供了 actions
這個可選的內置概念。 如果你現在就想要了解如何編寫 actions,請閱讀 Actions 章節。很簡單! 使用 actions
是有優勢的: 它們可以幫助你把代碼組織的更好,還能在狀態何時何地應該被修改這個問題上幫助你做出明智的決定。
MobX: 簡單且可擴展
MobX 是狀態管理庫中侵入性最小的之一。這使得 MobX
的方法不但簡單,而且可擴展性也非常好:
使用類和真正的引用
使用 MobX 不需要使數據標准化。這使得庫十分適合那些異常復雜的領域模型(以 Mendix 為例: 一個應用中有大約500個領域類)。
保證參照完整性
因為數據不需要標准化,所以 MobX 會自動跟蹤狀態和衍生之間的關系,你可以免費獲得參照完整性。渲染通過三級間接尋址訪問的數據?
沒有問題,MobX 會跟蹤它們,一旦其中一個引用發生了變化,就會重新渲染。作為回報,陳年的老bug已不復存在。作為一個程序員,你可能記不住修改的一些數據可能會影響到的某個角落里看起來毫不相關的組件,但 MobX 不會。
更簡單的 actions 更便於維護
正如上面所演示的,使用 MobX 修改狀態是非常簡單的。你只需簡單的寫出你的目的。MobX 會替你處理好剩下的事情。
細粒度的可觀測性是高效的
MobX 構建應用中所有衍生的圖形,以找到保持最新狀態所需的重新計算的最少次數。“衍生一切”或許聽上去開銷很昂貴,但 MobX 構建虛擬衍生圖以保持衍生與狀態同步所需的重計算的數量最小化。
事實上,在 Mendix 測試 MobX 時我們發現使用這個庫跟蹤代碼中的關系通常會更有效,而不是通過使用手寫事件或基於容器組件的“智能”選擇器來推送更改。
簡單來說,是因為 MobX 會在數據上建立更細粒度的“監聽器”,而不是通過程序來控制。
其次, MobX 看到衍生之間的因果關系,因此它可以為衍生排序,使得衍生不會運行多次或引入缺陷。
想了解這是如何工作的? 請參見 深入剖析 MobX。
易操作性
MobX 使用原生 javascript 。由於它的侵入性不強,它可以和絕大部分 javascript 庫共同使用,而不需要特定的 MobX 風格庫。
所以你可以繼續使用你的路由,數據獲取和工具庫,比如react-router
、 director
、 superagent
、 lodash
,等等。
出於同樣的原因,你可以在服務器端和客戶端使用它,也可以在 react-native 這樣的同構應用中使用。
結論就是: 相比其它狀態管理解決方案,當使用 MobX 時通常只需學習更少的新概念。