一篇帶你揭秘 MobX 實現(xiàn)原理
mobx 是流行的狀態(tài)管理庫,熱度僅次于 redux。它和 redux 有的地方一樣,也有的地方不一樣:
一樣的地方是 mobx 和 redux 都是單向數(shù)據(jù)流,通過 action 觸發(fā)全局 state 更新,然后通知視圖。
redux 的數(shù)據(jù)流:
mobx 的數(shù)據(jù)流:
但是它們修改狀態(tài)的方式不一樣:
redux 是每次返回一個全新的狀態(tài),一般搭配實現(xiàn)對象 immutable 的庫來用。
mobx 每次都是修改的同一個狀態(tài)對象,基于響應(yīng)式代理,也就是 Object.defineProperty 代理 get、set 的處理,get 時把依賴收集起來,set 修改時通知所有的依賴做更新。和 vue2 的響應(yīng)式代理很類似。
其中,redux 那種方式是函數(shù)式的思路,所以狀態(tài)的修改都在一個個 reducer 函數(shù)里,而 mobx 那種方式則是面向?qū)ο蟮拇淼乃悸罚院苋菀装? state 組織成一個個 class。
這也就導(dǎo)致了兩種狀態(tài)管理方式的代碼組織是有區(qū)別的:
redux 是在 reducer 函數(shù)里組織狀態(tài)(函數(shù)式的特點):
const reducer = (state = 0, action) => {
switch (action.type) {
case 'INCREMENT': return state + 1;
case 'DECREMENT': return state - 1;
default: return state;
}
};
而 mobx 則是在 class 里組織狀態(tài)(面向?qū)ο蟮奶攸c):
import {observable, action} from 'mobx';
class Store {
@observable number = 0;
@action add = () => {
this.number++;
}
}
此外,redux 那種方式每次都要返回一個新的對象,雖然可以用 immutable 的庫來減少創(chuàng)建新對象的開銷,但是比起 mobx 直接修改原對象來說,開銷還是大一點。
而且 redux 通知依賴更新的時候是全部通知的,而 mobx 因為收集了每個屬性的依賴,可以精準(zhǔn)的通知。
所以 mobx 的性能會比 redux 高一些。
綜上,mobx 和 redux 都是單向數(shù)據(jù)流,但是管理狀態(tài)的思路上,一個是函數(shù)式的思想,通過 reducer 函數(shù)每次返回新的 state,一個是面向?qū)ο蟮乃枷耄ㄟ^響應(yīng)式對象來管理狀態(tài),這導(dǎo)致了狀態(tài)組織方式上的不同(function/class),而且 redux 創(chuàng)建新的 state 的開銷還有通知所有依賴的開銷都比 mobx 大,性能比 mobx 差一些。
對比下來,我們會發(fā)現(xiàn) mobx 似乎比 redux 優(yōu)秀一些。那我們具體看下 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 基于響應(yīng)式對象來管理狀態(tài),所以組織狀態(tài)是用 class 的形式。
我們聲明了 Timer 的 class,有一個屬性是 secondsPassed 代表過去了幾秒,有兩個方法來修改它。
在構(gòu)造器里調(diào)用 makeAutoObservable 來創(chuàng)建響應(yīng)式的代理。
然后 new 一個 Timer 的對象,傳到組件里,組件使用 observer 的高階組件包裹,它負(fù)責(zé)把被包裹的組件添加到 timer 的響應(yīng)式依賴中去。
然后把這個組件渲染到 dom。
這樣就完成了 mobx 和 react 的結(jié)合使用,看下效果:
我們是把時間(secondsPassed)放在 mobx 的全局 state 中管理的,在組件里使用,然后定時更新它。發(fā)現(xiàn)每次更新組件都得到了通知并做了渲染,這就是全局狀態(tài)管理的功能。
demo 里我們用的 makeAutoObservable 函數(shù),它會自動給屬性添加響應(yīng)式代理,方法會添加一層觸發(fā) action 的代理。
也可以手動標(biāo)識:
import { observable, action } from "mobx"
class Timer {
@observable secondsPassed = 0
constructor() {
}
@action increase() {
this.secondsPassed += 1
}
@action reset() {
this.secondsPassed = 0
}
}
我們大概知道了 mobx 怎么用,那它是怎么實現(xiàn)的呢?
接下來我們從源碼來理一下它的實現(xiàn)原理:
mobx 的實現(xiàn)原理
首先,mobx 會對對象做響應(yīng)式代理,那代理以后的對象是什么樣的呢?
我們打印下:
原始對象的 secondsPassed 屬性是 0,increase 和 reset 方法體修改 secondsPassed 的值。
而代理以后的對象屬性分為了 get 和 set,并且實現(xiàn)變成了 this[$mobx].getObservablePropValue 和 setObservablePropValue,這明顯是做了響應(yīng)式的處理了。
代理以后的方法都變成了 excuteAction,執(zhí)行方法會 dispatch 一個 acition。
那這個響應(yīng)式的代理是怎么實現(xiàn)的呢?
跟蹤 makeAutoObservable 的源碼會發(fā)現(xiàn) mobx 創(chuàng)建了一個 ObservableObjectAdministration 的對象放到了 $mobx 屬性上。
在 timer 對象確實是有這個屬性的:
用 Symbol 聲明了一個私有屬性 mobx administration 來放 ObservableObjectAdministration 對象。
然后還用 Symbol 聲明了一個私有屬性。mobx-keys 來放所有做了代理的屬性和方法名。
那這個 ObservableObjectAdministration 對象是干啥的呢?
看下它的定義:
可以看到它有 values 屬性記錄每個 key 的依賴。
還有 getObservableValue 和 setObservableValue 來獲取和設(shè)置某個 key 的值。這兩個方法就是被代理后的屬性的 get set 最終調(diào)用的方法:
這不就串起來了么:
創(chuàng)建對象的時候 mobx 會對屬性和方法做代理,會添加一個 Symbol(mobx administrator) 屬性到對象上來保存 ObservableObjectAdministration 對象,它是用來記錄屬性的所有依賴的,對屬性的 get 和 set 都會被代理到這個 ObservableObjectAdministration 的 getXxx 和 setXxx 方法上。
我們打印下這個對象看看:
確實,values 里保存了唯一一個屬性和它的所有依賴。
至此,對對象做響應(yīng)式代理的流程我們已經(jīng)理清了:
那這個依賴是什么時候收集的呢?
我們繼續(xù)往下看 get 收集依賴和 set 觸發(fā)依賴更新的部分:
我們用 observable 包裹了組件,它是一個高階組件,對組件做一層代理,返回新的組件:
在這層代理里面,創(chuàng)建了 Reaction 對象,也就是收到更新的通知之后怎么做出反應(yīng),在回調(diào)函數(shù)里用 setState([]) 的方式實現(xiàn)了強(qiáng)制更新。
并且,這層高階組件的代理里會把當(dāng)前組件設(shè)置到全局,這樣后面做 get 的依賴收集的時候就能拿到對應(yīng)的組件了。
所以在組件里用到 state 的 get,做依賴收集時,就知道當(dāng)前是哪個組件了:
當(dāng)然,這里收集的不是具體哪個組件,而是 onInvalidate 的回調(diào)函數(shù),也就是收到更新的通知之后如何做出反應(yīng)。
這樣就完成了依賴的收集,在后面修改響應(yīng)式對象的狀態(tài)屬性的時候,就會觸發(fā)依賴,然后實現(xiàn)組件的更新:
這樣,我們就串聯(lián)起了 mobx 的響應(yīng)式原理:
總結(jié)
mobx 是熱度僅次于 redux 的狀態(tài)管理庫,它和 redux 有相同的地方也有不同的地方:
相同的地方是都是單向數(shù)據(jù)流。
不同的地方是 redux 是函數(shù)式思想的實現(xiàn),通過 reducer 函數(shù)管理狀態(tài),一般會用 immutable 的庫來提高創(chuàng)建新對象的性能。而 mobx 是面向?qū)ο蟮乃枷?,通過響應(yīng)式代理來管理狀態(tài),可以通過 class 組織 state。
性能方面 mobx 的響應(yīng)式能精準(zhǔn)的通知依賴做更新,而 redux 只能全局通知,而且 mobx 只是修改同一個對象,不是每次創(chuàng)建新對象,性能會比 redux 更高。
然后我們又通過一個 demo 來入門了下 react 中使用 mobx:通過 class 組織狀態(tài),然后創(chuàng)建響應(yīng)式代理,組件用 observer 高階組件做一層包裝,傳入 mobx 的對象,這樣 mobx 和組件就結(jié)合到了一起,狀態(tài)更新就能通知到組件。
之后我們從源碼層面理清了 mobx 的響應(yīng)式機(jī)制的實現(xiàn)原理:mobx 會在對象上添加一個 Symbol($mobx) 的隱藏屬性,用來放 ObservableObjectAdministration 對象,它是用于管理屬性和它的依賴的,在 get 的 時候收集依賴,然后 set 的時候就可以通知所有收集到的依賴(Reaction)做更新。
看到這里,你是否對 mobx 的特點和原理有更深的理解了呢?