MobX 上手指南

以前用 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 中就是一個 gettergetter 依賴的值一旦發生變化,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
}

warn

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 還提供了更精細化的監聽方法:reactionwhen

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 的實現,等我研究好了,再寫篇文章來分享。

image

相關文章
相關標籤/搜索