如何評價數據流管理框架 MobX ?
GitHub - mobxjs/mobx: Simple, scalable state management.
有人認為redux更適合大型應用,我是不同意的
非同步請求一多,redux所保證的可預測的數據變化根本就是在放屁,幾十個順序亂的action在devtools里,根本就沒有可預測可言redux要求數據不可變,但是一個reducer走完了之後,只知道數據變了,但是不知道具體哪裡變了,只能臟檢測(還有人拿react嘲諷ng的臟檢測真是呵呵)。那麼問題來了,如果有幾百個組件掛在那,光connect就幾百次臟檢測去了,如果有來自不同地方的數據,那麼這裡連利用不可變的數據來減少臟檢測都不行,有n個這樣的props就是n*幾百次臟檢測所以項目大到一定程度的話,用redux想要按需更新都不容易,更不用說一顆巨大的數據樹上在最末端的子節點發生數據更新,光不可變數據複製帶來的開銷會有多少mobx就很容易了,我只要保證在正確的地方觸發getter和setter,按需更新就不用我管了
還有一個問題,如果我要更新一個巨大的列表中的一個數據,列表項靠一個id區分,那麼你是打算每次更新去遍歷列表,還是人肉維護id和index的關係呢,還是orderedmap呢?mobx好辦,數據可變,我只要在合適的地方保持一份列表項的引用就好了,很多時候就是個閉包的事不用說redux一坨坨巨大的switch帶來的性能問題(別扯什麼純函數,別人fp語言是靠模式匹配的)action區分靠字元串,沒有adt的語言強行學fp語言那一套,累不累?大家對 MobX 的優點都總結得差不多了,知乎也有很多好文章總結其範式和實現,推薦沒用過的人學習一下這種類 vue 的狀態管理。
但是還是想給入坑的朋友提個醒,大家在安利的時候,可能不太會提這個庫的缺點,但這對於他人選型來說還是很有用的。
現在開始吐槽一下 MobX 的缺陷,希望能拋磚引玉。
如果你還沒有學過 MobX ,可以看這個例子理解一下:
import { observable, autorun } from "mobx"
let ob = observable({ a: 1 }) // 創建一個可觀察對象
// autorun 類似於 observe,會響應 ob.a 的變化自動執行
autorun(() =&> {
console.log("autorun", ob.a)
})
ob.a = 2
// autorun 2
一般的用法差不多就是
- 創建一個「可觀察對象」
- 用 autorun(handler) 包裹一個函數,handler 會在其內部的可觀察對象變化後自動響應執行。
- 修改可觀察對象,handler 會再次自動響應
看起來要比 redux 少寫很多代碼,其背後的響應式理念也十分貼心。但 mobx 的這種方便之處,其實引起了很多的問題。這些問題不全是因為 mobx 實現的問題,更多和它暴露的 api 有關係,導致你會出一些「想當然」的錯誤。這種「想當然」,是你基於之前的編程經驗,以為這樣寫能 work ,實際不能 work 導致的,這種時候你就會想怪罪庫了。
大多數可能預見的坑,它都寫在文檔裡面提醒你了,理解 MobX 對什麼有反應。看了這個長長的文檔你就知道,想寫出一個能讓 autorun 好好工作的、不出 bug 的 mobx 應用需要排多少雷了(求大牛科普肯定不會踩雷的寫作姿勢)。
你可以好好看下那個文檔,下面稍微提幾個開發中容易遇到的坑。
# autorun 收集不到依賴
import { observable, autorun } from "mobx"
let x = 1
let ob = observable({ a: 1, b: 1 })
let sub1 = autorun(() =&> {
console.log("log ob.a", ob)
})
let sub2 = autorun(() =&> {
console.log("log ob", ob.a)
})
let sub3 = autorun(() =&> {
if (ob.b === 2) {
console.log("判斷 ob.b", ob.a)
}
})
let sub4 = autorun(() =&> {
if (x === 2) {
console.log("判斷 x", ob.a)
}
})
x = 2
ob.b = 2
// 判斷 ob.b 1
ob.a = 3
// 判斷 ob.b 3
// log ob.a 3
上面四種代碼都是你非常有可能寫出來的代碼(你真的會有這種需求),但可以觀察到 sub1 和 sub4 掛了,為什麼呢?
因為 autorun 本質是做「依賴收集」的,執行 autorun(handler) 的時候,就會執行一遍handler。不摳實現細節,你可以假想存在 const o = { a: 1 }; let ob = mobx.observable(o) 會給 o 的屬性 a 在 ob 中設置對應的 getter/setter ,而 autorun 的 handler 中,當我們調用 ob.a 時,就會觸發設置的 getter,從而收集到這個依賴,使得 ob.a 被 set 之後,能通知 mobx 再次調用這個 autorun 的 handler。
所以在
- 聲明 sub1 的時候,我們沒有訪問 ob.a ,而只是訪問了 ob,autorun 沒有收集到 ob.a 的依賴。
- 同理,聲明 sub4 時,autorun 執行一遍 handler ,發現此時 x 是 1,沒有執行後續的 console.log,於是也沒有收集到任何依賴。即使以後 x 確是 === 2 ,這個 handler 也不會被執行第二次了...
知道了原理我們還是能避免寫出 bug 的,但是這些「bug」未免也太像常規操作了不是嗎?實踐中你很容易在某處加個判斷(尤其是加的層級比較深的時候),甚至一些非同步操作,導致 autorun 收集(預期的)依賴失敗,這些憑肉眼和直覺還是很難發現的。
## observable 和 plain object 容易混淆
mobx 為了使用方便,故意把訪問操作 observable 對象的方式設計得和 plain object 很像,完全無感。但也引起了一些問題,使用者會混淆兩者。
const x = observable([1, 2, 3])
x.map(x =&> x + 1) // 我們就像普通的數組一樣操作 observable array
// but...
if (Array.isArray(x)) {
x.push(4) // 不會被執行,因為 Array.isArray(observable([1, 2, 3])) === false
}
當然這是有解決方案的,比如 mobx 有轉型方法,或者上 TypeScript (這下你知道為什麼大多數靠譜的 mobx 應用都是用 ts 寫的了吧...),或者約定 mobx 對象都要 $ 開頭
redux 里用了 immutable.js 也有這種問題,不過後者還是在 api 上和 plain object 不同的,用 .get .set 才能訪問和修改,使用起來你就能體會不同。
我個人對 mobx 背後的響應式理念非常喜歡,但是 mobx 顯然還不夠好,這些問題導致要想用好 mobx ,一方面要求你懂 mbox.autorun 進行依賴收集、observable 數據結構的原理,另一方面要求你要有良好的記憶力,才能時時刻刻記得規避寫出 bug。也或者是我還沒找到一個更好的書寫姿勢( 求大佬送佛送到西,安利完了以後給個避雷針保平安)。
回頭再比較 redux。redux 寫起來只是煩而已,數據流還是很清晰的。不懂 redux 底層原理也不要緊,最多寫出性能不佳的代碼。但是不懂 mobx 依賴收集的原理可不行,很有可能就直接寫出 bug 了。
當然不是說就寫原生的 redux 吧,redux + redux-observable 其實也可以試試來著,如果不介意引入 Rx 的話。
我們團隊在用,確實比redux好用很多,但是也踩過不少坑,自己做了一個關於mobx的ppt, 比較詳細的介紹了mobx中的api和可能會遇到的問題,有在線演示,地址在:https://github.com/ckinmind/mobx-share, 歡迎star
網上看到有人說要redux轉mobx了,心情複雜,變來變去,不是老人家一直在用的東西嗎?
observable data 一直在JS里,很老的jquery里,就用var states = $({})
states.on("foo", function (data) { ... })
states.trigger("foo", data)
雖然外形上看起來這不像data更像event,但稍加改進就可以變成一個observable data store。
對於選擇 React 技術棧來說,可以終於不用折騰 Redux 了,算是中小型應用的福音吧,但是目前中文資料較少,官方的文檔看起來並不是很友好。
再回頭說幾句,其實大部分基於 React 的應用都用不著 Redux ,因為業務規模根本就沒達到量級。React 本身夠簡單,而加上 Redux 後複雜度直線上升,至於很多人用是因為根本沒有太多選擇或是有盲目跟風的嫌疑。不用想了,mobx很不錯,用在項目裡面管理狀態非常OK。本人兩個大點的項目都是用mobx,以前用redux。
與 Redux 對比 Mobx 簡單太多了,只要掌握 observable、observer、toJS、transaction等幾個API就可以輕鬆上手,之後再進階地理解下 computed、reaction、autoRun 等幾個概念後對付一般中小型項目就完全ok了。合理使用它的話也無須做刻意的性能優化。
出於快速使用React開發業務的目的,最近幾個項目一直都用Mobx。另外我們使用Mobx與React生態其他優秀項目如Ant Design、React-Dnd等配合也是完全沒有問題的。
現在Mobx的中文資料其實也不少了,比2016年剛火起來時多了很多。對於個人項目和小型項目 推薦MobX 理由簡單 可控
另附上MobX官方指定中文文檔: http://cn.mobx.js.org
希望對MobX感興趣的同學有所幫助
介於react+redux 與 vue+vuex 之間的方案,感覺不錯。
目前在自己項目裡面開始實踐了,相對Redux確實輕便很多,不過還待深入學習~
推薦閱讀: