之前用 Redux 比較多,一直聽說 Mobx 能讓你體驗到在 React 里面寫 Vue 的感覺,今天打算嘗試下 Mobx 是不是真的有寫 Vue 的感覺。
題外話
在介紹 MobX 的用法之前,先說點題外話,我們可以看一下 MobX 的中文簡介。在 MobX 的中文網站上寫著:
“MobX 是一個經過戰火洗禮的庫,它通過透明的函數響應式編程使得狀態管理變得簡單和可擴展。
“戰火洗禮的庫” 怎么看都感覺很奇怪,讀起來很拗口????,而且網上很多介紹 MobX 的文章都是這么寫的,在 github 翻閱其 README 發現寫的是:
“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。
響應式對象
MobX 通過 makeObservable
方法來構造響應式對象,傳入的對象屬性會通過 ?Proxy
代理,與 Vue 類似,在 6.0 版本之前使用的是 ? Object.defineProperty
?API,當然 6.0 也提供了降級方案。
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,使用裝飾器的寫法:
class?Store?{@observable?count?=?0constructor()?{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
,可以自動為屬性加上對象的包裝函數,上手成本直線下降。
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()?{?//?actionthis.count?+=?1},decrement()?{?//?actionthis.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)})//?獲取數據后,調用已有的?actionthis.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 的實現,等我研究好了,再寫篇文章來分享。
最近組建了一個江西人的前端交流群,如果你也是江西人可以加我微信ruochuan12 拉你進群。
常駐推薦閱讀
若川知乎高贊:有哪些必看的 JS庫?
我在阿里招前端,我該怎么幫你?(現在還可以加模擬面試群)
如何拿下阿里巴巴 P6 的前端 Offer
如何準備阿里P6/P7前端面試--項目經歷準備篇
大廠面試官常問的亮點,該如何做出?
如何從初級到專家(P4-P7)打破成長瓶頸和有效突破
若川知乎問答:2年前端經驗,做的項目沒什么技術含量,怎么辦?
常駐末尾
你好,我是若川,江西人~(點擊藍字了解我)歷時一年只寫了一個學習源碼整體架構系列?有哪些必看的JS庫:jQuery、underscore、lodash、sentry、vuex、axios、koa、redux
關注
若川視野
,回復"pdf" 領取優質前端書籍pdf,回復"1",可加群長期交流學習我的博客地址:https://lxchuan12.gitee.io?歡迎收藏
覺得文章不錯,可以?分享、點贊、在看?呀^_^另外歡迎
留言
交流~
小提醒:若川視野公眾號面試、源碼等文章合集在菜單欄中間
【源碼精選】
按鈕,歡迎點擊閱讀,也可以星標我的公眾號,便于查找