一篇帶你揭秘 MobX 實現原理

mobx 是流行的狀態管理庫,熱度僅次于 redux。它和 redux 有的地方一樣,也有的地方不一樣:
一樣的地方是 mobx 和 redux 都是單向數據流,通過 action 觸發全局 state 更新,然后通知視圖。
redux 的數據流:

mobx 的數據流:

但是它們修改狀態的方式不一樣:
redux 是每次返回一個全新的狀態,一般搭配實現對象 immutable 的庫來用。
mobx 每次都是修改的同一個狀態對象,基于響應式代理,也就是 Object.defineProperty 代理 get、set 的處理,get 時把依賴收集起來,set 修改時通知所有的依賴做更新。和 vue2 的響應式代理很類似。
其中,redux 那種方式是函數式的思路,所以狀態的修改都在一個個 reducer 函數里,而 mobx 那種方式則是面向對象的代理的思路,所以很容易把 state 組織成一個個 class。
這也就導致了兩種狀態管理方式的代碼組織是有區別的:
redux 是在 reducer 函數里組織狀態(函數式的特點):
const reducer = (state = 0, action) => {
switch (action.type) {
case 'INCREMENT': return state + 1;
case 'DECREMENT': return state - 1;
default: return state;
}
};
而 mobx 則是在 class 里組織狀態(面向對象的特點):
import {observable, action} from 'mobx';
class Store {
@observable number = 0;
@action add = () => {
this.number++;
}
}
此外,redux 那種方式每次都要返回一個新的對象,雖然可以用 immutable 的庫來減少創建新對象的開銷,但是比起 mobx 直接修改原對象來說,開銷還是大一點。
而且 redux 通知依賴更新的時候是全部通知的,而 mobx 因為收集了每個屬性的依賴,可以精準的通知。
所以 mobx 的性能會比 redux 高一些。
綜上,mobx 和 redux 都是單向數據流,但是管理狀態的思路上,一個是函數式的思想,通過 reducer 函數每次返回新的 state,一個是面向對象的思想,通過響應式對象來管理狀態,這導致了狀態組織方式上的不同(function/class),而且 redux 創建新的 state 的開銷還有通知所有依賴的開銷都比 mobx 大,性能比 mobx 差一些。
對比下來,我們會發現 mobx 似乎比 redux 優秀一些。那我們具體看下 mobx 怎么用吧:
mobx 的使用
官方提供的 demo 是這樣的:
import React from "react"
import ReactDOM from 'react-dom';
import { makeAutoObservable } from "mobx"
import { observer } from "mobx-react"
class Timer {
secondsPassed = 0
constructor() {
makeAutoObservable(this)
}
increase() {
this.secondsPassed += 1
}
reset() {
this.secondsPassed = 0
}
}
const myTimer = new Timer()
const TimerView = observer(({ timer }) => (
<button onClick={() => timer.reset()}>Seconds passed: {timer.secondsPassed}</button>
))
setInterval(() => {
myTimer.increase()
}, 1000);
ReactDOM.render(<TimerView timer={myTimer} />, document.getElementById('root'));
就像前面說的,mobx 基于響應式對象來管理狀態,所以組織狀態是用 class 的形式。
我們聲明了 Timer 的 class,有一個屬性是 secondsPassed 代表過去了幾秒,有兩個方法來修改它。
在構造器里調用 makeAutoObservable 來創建響應式的代理。
然后 new 一個 Timer 的對象,傳到組件里,組件使用 observer 的高階組件包裹,它負責把被包裹的組件添加到 timer 的響應式依賴中去。
然后把這個組件渲染到 dom。
這樣就完成了 mobx 和 react 的結合使用,看下效果:

我們是把時間(secondsPassed)放在 mobx 的全局 state 中管理的,在組件里使用,然后定時更新它。發現每次更新組件都得到了通知并做了渲染,這就是全局狀態管理的功能。
demo 里我們用的 makeAutoObservable 函數,它會自動給屬性添加響應式代理,方法會添加一層觸發 action 的代理。
也可以手動標識:
import { observable, action } from "mobx"
class Timer {
@observable secondsPassed = 0
constructor() {
}
@action increase() {
this.secondsPassed += 1
}
@action reset() {
this.secondsPassed = 0
}
}
我們大概知道了 mobx 怎么用,那它是怎么實現的呢?
接下來我們從源碼來理一下它的實現原理:
mobx 的實現原理
首先,mobx 會對對象做響應式代理,那代理以后的對象是什么樣的呢?
我們打印下:

原始對象的 secondsPassed 屬性是 0,increase 和 reset 方法體修改 secondsPassed 的值。
而代理以后的對象屬性分為了 get 和 set,并且實現變成了 this[$mobx].getObservablePropValue 和 setObservablePropValue,這明顯是做了響應式的處理了。
代理以后的方法都變成了 excuteAction,執行方法會 dispatch 一個 acition。
那這個響應式的代理是怎么實現的呢?
跟蹤 makeAutoObservable 的源碼會發現 mobx 創建了一個 ObservableObjectAdministration 的對象放到了 $mobx 屬性上。

在 timer 對象確實是有這個屬性的:

用 Symbol 聲明了一個私有屬性 mobx administration 來放 ObservableObjectAdministration 對象。
然后還用 Symbol 聲明了一個私有屬性。mobx-keys 來放所有做了代理的屬性和方法名。
那這個 ObservableObjectAdministration 對象是干啥的呢?
看下它的定義:

可以看到它有 values 屬性記錄每個 key 的依賴。
還有 getObservableValue 和 setObservableValue 來獲取和設置某個 key 的值。這兩個方法就是被代理后的屬性的 get set 最終調用的方法:

這不就串起來了么:
創建對象的時候 mobx 會對屬性和方法做代理,會添加一個 Symbol(mobx administrator) 屬性到對象上來保存 ObservableObjectAdministration 對象,它是用來記錄屬性的所有依賴的,對屬性的 get 和 set 都會被代理到這個 ObservableObjectAdministration 的 getXxx 和 setXxx 方法上。
我們打印下這個對象看看:

確實,values 里保存了唯一一個屬性和它的所有依賴。
至此,對對象做響應式代理的流程我們已經理清了:

那這個依賴是什么時候收集的呢?
我們繼續往下看 get 收集依賴和 set 觸發依賴更新的部分:
我們用 observable 包裹了組件,它是一個高階組件,對組件做一層代理,返回新的組件:

在這層代理里面,創建了 Reaction 對象,也就是收到更新的通知之后怎么做出反應,在回調函數里用 setState([]) 的方式實現了強制更新。

并且,這層高階組件的代理里會把當前組件設置到全局,這樣后面做 get 的依賴收集的時候就能拿到對應的組件了。


所以在組件里用到 state 的 get,做依賴收集時,就知道當前是哪個組件了:


當然,這里收集的不是具體哪個組件,而是 onInvalidate 的回調函數,也就是收到更新的通知之后如何做出反應。
這樣就完成了依賴的收集,在后面修改響應式對象的狀態屬性的時候,就會觸發依賴,然后實現組件的更新:

這樣,我們就串聯起了 mobx 的響應式原理:

總結
mobx 是熱度僅次于 redux 的狀態管理庫,它和 redux 有相同的地方也有不同的地方:
相同的地方是都是單向數據流。
不同的地方是 redux 是函數式思想的實現,通過 reducer 函數管理狀態,一般會用 immutable 的庫來提高創建新對象的性能。而 mobx 是面向對象的思想,通過響應式代理來管理狀態,可以通過 class 組織 state。
性能方面 mobx 的響應式能精準的通知依賴做更新,而 redux 只能全局通知,而且 mobx 只是修改同一個對象,不是每次創建新對象,性能會比 redux 更高。
然后我們又通過一個 demo 來入門了下 react 中使用 mobx:通過 class 組織狀態,然后創建響應式代理,組件用 observer 高階組件做一層包裝,傳入 mobx 的對象,這樣 mobx 和組件就結合到了一起,狀態更新就能通知到組件。
之后我們從源碼層面理清了 mobx 的響應式機制的實現原理:mobx 會在對象上添加一個 Symbol($mobx) 的隱藏屬性,用來放 ObservableObjectAdministration 對象,它是用于管理屬性和它的依賴的,在 get 的 時候收集依賴,然后 set 的時候就可以通知所有收集到的依賴(Reaction)做更新。
看到這里,你是否對 mobx 的特點和原理有更深的理解了呢?

























