聊聊MobX 上手指南
之前用 Redux 比較多,一直聽(tīng)說(shuō) Mobx 能讓你體驗(yàn)到在 React 里面寫(xiě) Vue 的感覺(jué),今天打算嘗試下 Mobx 是不是真的有寫(xiě) Vue 的感覺(jué)。
題外話
在介紹 MobX 的用法之前,先說(shuō)點(diǎn)題外話,我們可以看一下 MobX 的中文簡(jiǎn)介。在 MobX 的中文網(wǎng)站上寫(xiě)著:
“MobX 是一個(gè)經(jīng)過(guò)戰(zhàn)火洗禮的庫(kù),它通過(guò)透明的函數(shù)響應(yīng)式編程使得狀態(tài)管理變得簡(jiǎn)單和可擴(kuò)展。
數(shù)據(jù)流
“戰(zhàn)火洗禮的庫(kù)” 怎么看都感覺(jué)很奇怪,讀起來(lái)很拗口??,而且網(wǎng)上很多介紹 MobX 的文章都是這么寫(xiě)的,在 github 翻閱其 README 發(fā)現(xiàn)寫(xiě)的是:
“MobX is a battle tested library that makes state management simple and scalable by transparently applying functional reactive programming (TFRP).
可以看到作者原本要表達(dá)的意思是 MobX 是經(jīng)過(guò)了許多的測(cè)試,擁有比較強(qiáng)的健壯性。下面是通過(guò)谷歌翻譯的結(jié)果,看起來(lái)也比中文網(wǎng)的表達(dá)要準(zhǔn)確一些。
谷歌翻譯
雖然,我的英文水平也很菜,還是會(huì)盡量看官方的文檔,這樣可以避免一些不必要的誤解。
如何使用?
言歸正傳,MobX 現(xiàn)在的最新版是 6.0,這個(gè)版本的 API 相比于之前有了極大的簡(jiǎn)化,可以說(shuō)更加好用了。之前的版本是裝飾器風(fēng)格的語(yǔ)法糖,但是裝飾器在現(xiàn)在的 ES 規(guī)范中并不成熟,而且引入裝飾器語(yǔ)法也在增加打包后的代碼體積。綜合考慮后,MobX 6.0 取消了裝飾器語(yǔ)法的 API。
響應(yīng)式對(duì)象
MobX 通過(guò) makeObservable 方法來(lái)構(gòu)造響應(yīng)式對(duì)象,傳入的對(duì)象屬性會(huì)通過(guò) Proxy 代理,與 Vue 類似,在 6.0 版本之前使用的是 Object.defineProperty API,當(dāng)然 6.0 也提供了降級(jí)方案。
- import { configure, makeObservable, observable, action, computed } from 'mobx'
- // 使用該配置,可以將 Proxy 降級(jí)為 Object.defineProperty
- configure({ useProxies: "never" });
- // 構(gòu)造響應(yīng)對(duì)象
- const store = makeObservable(
- // 需要代理的響應(yīng)對(duì)象
- {
- count: 0,
- get double() {
- return this.count * 2
- },
- increment() {
- this.count += 1
- },
- decrement() {
- this.count -= 1
- }
- },
- // 對(duì)各個(gè)屬性進(jìn)行包裝,用于標(biāo)記該屬性的作用
- {
- count: observable, // 需要跟蹤的響應(yīng)屬性
- double: computed, // 計(jì)算屬性
- increment: action, // action 調(diào)用后,會(huì)修改響應(yīng)對(duì)象
- decrement: action, // action 調(diào)用后,會(huì)修改響應(yīng)對(duì)象
- }
- )
我們?cè)诳纯粗鞍姹镜?MobX,使用裝飾器的寫(xiě)法:
- 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()
這么看起來(lái),好像寫(xiě)法并沒(méi)有得到什么簡(jiǎn)化,好像比寫(xiě)裝飾器還要復(fù)雜點(diǎn)。下面我們看看 6.0 版本一個(gè)更強(qiáng)大的 API:makeAutoObservable。
makeAutoObservable 是一個(gè)更強(qiáng)大的 makeObservable,可以自動(dòng)為屬性加上對(duì)象的包裝函數(shù),上手成本直線下降。
- import { makeAutoObservable } from 'mobx'
- const store = makeAutoObservable({
- count: 0,
- get double() {
- return this.count * 2
- },
- increment() {
- this.count += 1
- },
- decrement() {
- this.count -= 1
- }
- })
計(jì)算屬性
MobX 的屬性與 Vue 的 computed 一樣,在 makeAutoObservable 中就是一個(gè) getter,getter 依賴的值一旦發(fā)生變化,getter 本身的返回值也會(huì)跟隨變化。
- import { makeAutoObservable } from 'mobx'
- const store = makeAutoObservable({
- count: 0,
- get double() {
- return this.count * 2
- }
- })
當(dāng) store.count 為 1 時(shí),調(diào)用 store.double 會(huì)返回 2。
修改行為
當(dāng)我們需要修改 store 上的響應(yīng)屬性時(shí),我們可以通過(guò)直接重新賦值的方式修改,但是這樣會(huì)得到 MobX 的警告??。
- const store = makeAutoObservable({
- count: 0
- });
- document.getElementById("increment").onclick = function () {
- store.count += 1
- }
warn
MobX 會(huì)提示,在修改響應(yīng)式對(duì)象的屬性時(shí),需要通過(guò) action 的方式修改。雖然直接修改也能生效,但是這樣會(huì)讓 MobX 狀態(tài)的管理比較混亂,而且將狀態(tài)修改放到 action 中,能夠讓 MobX 在內(nèi)部的事務(wù)流程中進(jìn)行修改,以免拿到的某個(gè)屬性還處于中間態(tài),最后計(jì)算的結(jié)果不夠準(zhǔn)確。
makeAutoObservable 中的所有方法都會(huì)被處理成 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,將狀態(tài)的修改劃分為 mutation 和 action,同步修改放到 mutation 中,異步的操作放到 action 中。在 MobX 中,不管是同步還是異步操作,都可以放到 action 中,只是異步操作在修改屬性時(shí),需要將賦值操作放到 runInAction 中。
- import { runInAction, makeAutoObservable } from 'mobx'
- const store = makeAutoObservable({
- count: 0,
- async initCount() {
- // 模擬獲取遠(yuǎn)程的數(shù)據(jù)
- const count = await new Promise((resolve) => {
- setTimeout(() => {
- resolve(10)
- }, 500)
- })
- // 獲取數(shù)據(jù)后,將賦值操作放到 runInAction 中
- runInAction(() => {
- this.count = count
- })
- }
- })
- store.initCount()
如果不調(diào)用 runInAction ,則可以直接調(diào)用本身已經(jīng)存在的 action。
- import { runInAction, makeAutoObservable } from 'mobx'
- const store = makeAutoObservable({
- count: 0,
- setCount(count) {
- this.count = count
- },
- async initCount() {
- // 模擬獲取遠(yuǎn)程的數(shù)據(jù)
- const count = await new Promise((resolve) => {
- setTimeout(() => {
- resolve(10)
- }, 500)
- })
- // 獲取數(shù)據(jù)后,調(diào)用已有的 action
- this.setCount(count)
- }
- })
- store.initCount()
監(jiān)聽(tīng)對(duì)象變更
無(wú)論是在 React 還是在小程序中想要引入 MobX,都需要在對(duì)象變更的時(shí)候,通知調(diào)用原生的 setState/setData 方法,將狀態(tài)同步到視圖上。
通過(guò) autorun 方法可以實(shí)現(xiàn)這個(gè)能力,我們可以把 autorun 理解為 React Hooks 中的 useEffect。每當(dāng) store 的響應(yīng)屬性發(fā)生修改時(shí),傳入 autorun 的方法(effect)就會(huì)被調(diào)用一次。
- 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}`
- })
每當(dāng) button#increment 按鈕被點(diǎn)擊的時(shí)候,span#count 內(nèi)的值就會(huì)自動(dòng)進(jìn)行同步。??查看完整代碼。
效果演示
除了 autorun ,MobX 還提供了更精細(xì)化的監(jiān)聽(tīng)方法:reaction、 when。
- const store = makeAutoObservable({
- count: 0,
- setCount(count) {
- this.count = count
- },
- increment() {
- this.count++
- },
- decrement() {
- this.count--
- }
- })
- // store 發(fā)生修改立即調(diào)用 effect
- autorun(() => {
- $count.innerText = `${store.count}`
- });
- // 第一個(gè)方法的返回值修改后才會(huì)調(diào)用后面的 effect
- reaction(
- // 表示 store.count 修改后才會(huì)調(diào)用
- () => store.count,
- // 第一個(gè)參數(shù)為當(dāng)前值,第二個(gè)參數(shù)為修改前的值
- // 有點(diǎn)類似與 Vue 中的 watch
- (value, prevValue) => {
- console.log('diff', value - prevValue)
- }
- );
- // 第一個(gè)方法的返回值為真,立即調(diào)用后面的 effect
- when(() => store.count > 10, () => {
- console.log(store.count)
- })
- // when 方法還能返回一個(gè) promise
- (async function() {
- await when(() => store.count > 10)
- console.log('store.count > 10')
- })()
總結(jié)
MobX 的介紹到這里就結(jié)束了,本文只是大致的列舉了一下 MobX 的 API,希望大家能有所收獲。后續(xù)打算再深入研究下 MobX 的實(shí)現(xiàn),等我研究好了,再寫(xiě)篇文章來(lái)分享。