以前用 Redux 比較多,一直據說 Mobx 能讓你體驗到在 React 裏面寫 Vue 的感受,今天打算嘗試下 Mobx 是否是真的有寫 Vue 的感受。react
在介紹 MobX 的用法以前,先說點題外話,咱們能夠看一下 MobX 的中文簡介。在 MobX 的中文網站上寫着:git
MobX 是一個通過戰火洗禮的庫,它經過透明的函數響應式編程使得狀態管理變得簡單和可擴展。
「戰火洗禮的庫」 怎麼看都感受很奇怪,讀起來很拗口😂,並且網上不少介紹 MobX 的文章都是這麼寫的,在 github 翻閱其 README 發現寫的是:github
MobX is a battle tested library that makes state management simple and scalable by transparently applying functional reactive programming (TFRP).
能夠看到做者本來要表達的意思是 MobX 是通過了許多的測試,擁有比較強的健壯性。下面是經過谷歌翻譯的結果,看起來也比中文網的表達要準確一些。編程
雖然,個人英文水平也很菜,仍是會盡可能看官方的文檔,這樣能夠避免一些沒必要要的誤解。小程序
言歸正傳,MobX 如今的最新版是 6.0,這個版本的 API 相比於以前有了極大的簡化,能夠說更加好用了。以前的版本是裝飾器風格的語法糖,可是裝飾器在如今的 ES 規範中並不成熟,並且引入裝飾器語法也在增長打包後的代碼體積。綜合考慮後,MobX 6.0 取消了裝飾器語法的 API。segmentfault
MobX 經過 makeObservable
方法來構造響應式對象,傳入的對象屬性會經過 Proxy
代理,與 Vue 相似,在 6.0 版本以前使用的是 Object.defineProperty
API,固然 6.0 也提供了降級方案。promise
import { configure, makeObservable, observable, action, computed } from 'mobx' // 使用該配置,能夠將 Proxy 降級爲 Object.defineProperty configure({ useProxies: "never" }); // 構造響應對象 const store = makeObservable( // 須要代理的響應對象 { count: 0, get double() { return this.count * 2 }, increment() { this.count += 1 }, decrement() { this.count -= 1 } }, // 對各個屬性進行包裝,用於標記該屬性的做用 { count: observable, // 須要跟蹤的響應屬性 double: computed, // 計算屬性 increment: action, // action 調用後,會修改響應對象 decrement: action, // action 調用後,會修改響應對象 } )
咱們在看看以前版本的 MobX,使用裝飾器的寫法:app
class Store { @observable count = 0 constructor() { makeObservable(this) } @action increment() { this.count++; } @action decrement() { this.count--; } @computed get double() { return this.count * 2 } } const store = new Store()
這麼看起來,好像寫法並無獲得什麼簡化,好像比寫裝飾器還要複雜點。下面咱們看看 6.0 版本一個更強大的 API:makeAutoObservable
。異步
makeAutoObservable
是一個更強大的 makeObservable
,能夠自動爲屬性加上對象的包裝函數,上手成本直線降低。async
import { makeAutoObservable } from 'mobx' const store = makeAutoObservable({ count: 0, get double() { return this.count * 2 }, increment() { this.count += 1 }, decrement() { this.count -= 1 } })
MobX 的屬性與 Vue 的 computed
同樣,在 makeAutoObservable
中就是一個 getter
,getter
依賴的值一旦發生變化,getter
自己的返回值也會跟隨變化。
import { makeAutoObservable } from 'mobx' const store = makeAutoObservable({ count: 0, get double() { return this.count * 2 } })
當 store.count
爲 1 時,調用 store.double
會返回 2。
當咱們須要修改 store 上的響應屬性時,咱們能夠經過直接從新賦值的方式修改,可是這樣會獲得 MobX 的警告⚠️。
const store = makeAutoObservable({ count: 0 }); document.getElementById("increment").onclick = function () { store.count += 1 }
MobX 會提示,在修改響應式對象的屬性時,須要經過 action 的方式修改。雖然直接修改也能生效,可是這樣會讓 MobX 狀態的管理比較混亂,並且將狀態修改放到 action 中,可以讓 MobX 在內部的事務流程中進行修改,以避免拿到的某個屬性還處於中間態,最後計算的結果不夠準確。
makeAutoObservable
中的全部方法都會被處理成 action。
import { makeAutoObservable } from 'mobx' const store = makeAutoObservable({ count: 0, get double() { return this.count * 2 }, increment() { // action this.count += 1 }, decrement() { // action this.count -= 1 } })
不一樣於 Vuex,將狀態的修改劃分爲 mutation 和 action,同步修改放到 mutation 中,異步的操做放到 action 中。在 MobX 中,不論是同步仍是異步操做,均可以放到 action 中,只是異步操做在修改屬性時,須要將賦值操做放到 runInAction
中。
import { runInAction, makeAutoObservable } from 'mobx' const store = makeAutoObservable({ count: 0, async initCount() { // 模擬獲取遠程的數據 const count = await new Promise((resolve) => { setTimeout(() => { resolve(10) }, 500) }) // 獲取數據後,將賦值操做放到 runInAction 中 runInAction(() => { this.count = count }) } }) store.initCount()
若是不調用 runInAction
,則能夠直接調用自己已經存在的 action。
import { runInAction, makeAutoObservable } from 'mobx' const store = makeAutoObservable({ count: 0, setCount(count) { this.count = count }, async initCount() { // 模擬獲取遠程的數據 const count = await new Promise((resolve) => { setTimeout(() => { resolve(10) }, 500) }) // 獲取數據後,調用已有的 action this.setCount(count) } }) store.initCount()
不管是在 React 仍是在小程序中想要引入 MobX,都須要在對象變動的時候,通知調用原生的 setState/setData
方法,將狀態同步到視圖上。
經過 autorun
方法能夠實現這個能力,咱們能夠把 autorun
理解爲 React Hooks 中的 useEffect
。每當 store 的響應屬性發生修改時,傳入 autorun
的方法(effect
)就會被調用一次。
import { autorun, makeAutoObservable } from 'mobx' const store = makeAutoObservable({ count: 0, setCount(count) { this.count = count }, increment() { this.count++ }, decrement() { this.count-- } }) document.getElementById("increment").onclick = function () { store.count++ } const $count = document.getElementById("count") $count.innerText = `${store.count}` autorun(() => { $count.innerText = `${store.count}` })
每當 button#increment
按鈕被點擊的時候,span#count
內的值就會自動進行同步。👉查看完整代碼。
除了 autorun
,MobX 還提供了更精細化的監聽方法:reaction
、 when
。
const store = makeAutoObservable({ count: 0, setCount(count) { this.count = count }, increment() { this.count++ }, decrement() { this.count-- } }) // store 發生修改當即調用 effect autorun(() => { $count.innerText = `${store.count}` }); // 第一個方法的返回值修改後纔會調用後面的 effect reaction( // 表示 store.count 修改後纔會調用 () => store.count, // 第一個參數爲當前值,第二個參數爲修改前的值 // 有點相似與 Vue 中的 watch (value, prevValue) => { console.log('diff', value - prevValue) } ); // 第一個方法的返回值爲真,當即調用後面的 effect when(() => store.count > 10, () => { console.log(store.count) }) // when 方法還能返回一個 promise (async function() { await when(() => store.count > 10) console.log('store.count > 10') })()
MobX 的介紹到這裏就結束了,本文只是大體的列舉了一下 MobX 的 API,但願你們能有所收穫。後續打算再深刻研究下 MobX 的實現,等我研究好了,再寫篇文章來分享。