Mobx——derivation 的依赖收集过程 (离职拷贝版
离职了,把 2019 年在公司写的文档 copy 出来。年头有点久,可能写的不太对,也不是很想改了~
注:本文档对应 mobx 版本为 4.15.4、mobx-vue 版本为 2.0.10
源码解析
1. autorun & reaction
先看一下 autonrun 和 reaction 的源码
function autorun(
view: (r: IReactionPublic) => any,
opts: IAutorunOptions = EMPTY_OBJECT
): IReactionDisposer {
...
if (runSync) {
reaction = new Reaction(
name,
function(this: Reaction) {
this.track(reactionRunner)
},
opts.onError,
opts.requiresObservable
)
} else {
const scheduler = createSchedulerFromOptions(opts)
// debounced autorun
let isScheduled = false
reaction = new Reaction(
name,
() => {
if (!isScheduled) {
isScheduled = true
scheduler(() => {
isScheduled = false
if (!reaction.isDisposed) reaction.track(reactionRunner)
})
}
},
opts.onError,
opts.requiresObservable
)
}
function reactionRunner() {
view(reaction)
}
reaction.schedule()
return reaction.getDisposer()
}
function reaction<T>(
expression: (r: IReactionPublic) => T,
effect: (arg: T, r: IReactionPublic) => void,
opts: IReactionOptions = EMPTY_OBJECT
): IReactionDisposer {
// 回调 effect 用 action 封装了一层
const effectAction = action(
name,
opts.onError ? wrapErrorHandler(opts.onError, effect) : effect
)
const r = new Reaction(
name,
() => {
if (firstTime || runSync) {
reactionRunner()
} else if (!isScheduled) {
isScheduled = true
scheduler!(reactionRunner)
}
},
opts.onError,
opts.requiresObservable
)
function reactionRunner() {
isScheduled = false
if (r.isDisposed) return
let changed = false
r.track(() => {
const nextValue = expression(r)
changed = firstTime || !equals(value, nextValue)
value = nextValue
})
if (firstTime && opts.fireImmediately!) effectAction(value, r)
if (!firstTime && (changed as boolean) === true) effectAction(value, r)
if (firstTime) firstTime = false
}
r.schedule()
return r.getDisposer()
}
其实就是 new 了一个 Reaction,只不过 autorun 把回调直接传进去 track 了,用起来比较无脑,而 reaction 则是把触发条件传进去 track,而回调则是用 action 包了一层,并且多一个 firstTime 参数来限制回调只能执行单次
这样带来的区别是什么?更加语义化的触发时机 + action 事务特性加持的回调函数
所以这里只需要搞懂 Reaction 就行了
2. Reaction
class Reaction implements IDerivation, IReactionPublic {
observing: IObservable[] = []
newObserving: IObservable[] = []
dependenciesState = IDerivationState.NOT_TRACKING
diffValue = 0
runId = 0
unboundDepsCount = 0
__mapid = "#" + getNextId()
isDisposed = false
_isScheduled = false
_isTrackPending = false
_isRunning = false
isTracing: TraceMode = TraceMode.NONE
constructor(
public name: string = "Reaction@" + getNextId(),
private onInvalidate: () => void,
private errorHandler?: (error: any, derivation: IDerivation) => void,
public requiresObservable = false
) {}
onBecomeStale() {
this.schedule()
}
schedule() {
if (!this._isScheduled) {
this._isScheduled = true
globalState.pendingReactions.push(this)
runReactions()
}
}
isScheduled() {
return this._isScheduled
}
runReaction() {
if (!this.isDisposed) {
startBatch() // globalState.inBatch++
this._isScheduled = false
if (shouldCompute(this)) {
this._isTrackPending = true
try {
this.onInvalidate() // 调用入参回调函数,也就是this.track(incomeFunction)
if (
this._isTrackPending &&
isSpyEnabled() &&
process.env.NODE_ENV !== "production"
) {
// onInvalidate didn't trigger track right away..
spyReport({
name: this.name,
type: "scheduled-reaction"
})
}
} catch (e) {
this.reportExceptionInDerivation(e)
}
}
endBatch()
}
}
track(fn: () => void) {
startBatch()
const notify = isSpyEnabled()
let startTime
if (notify) {
startTime = Date.now()
spyReportStart({
name: this.name,
type: "reaction"
})
}
this._isRunning = true
// 依赖收集的核心
const result = trackDerivedFunction(this, fn, undefined)
this._isRunning = false
this._isTrackPending = false
if (this.isDisposed) {
clearObserving(this)
}
if (isCaughtException(result)) this.reportExceptionInDerivation(result.cause)
if (notify && process.env.NODE_ENV !== "production") {
spyReportEnd({
time: Date.now() - startTime
})
}
endBatch()
}
...
}
然后追一下 reaction(expression, effect, opts) 里的调用顺序
- 利用 action 和 第二个入参 effect (待执行的回调函数)定义 effectAction
- const r = new Reaction
- r.schedule 也就是 Reaction.schedule
- Reaction.runReaction
- this.onInvalidate, 这里 onInvalidate 是 new Reaction 里 第二个参数,传进来的是 r.track()
- track 里面的核心内容是trackDerivedFunction,具体源码如下:
3. trackDerivedFunction
trackDerivedFunction(this, fn, undefined) 这里 fn 就是 reaction 里 第一个入参(依赖相关的条件函数) expression 对应的操作:
const nextValue = expression(r)
changed = firstTime || !equals(value, nextValue)
value = nextValue
// 追下 trackDerivedFunction 都干了啥
function trackDerivedFunction<T>(derivation: IDerivation, f: () => T, context: any) {
const prevAllowStateReads = allowStateReadsStart(true)
changeDependenciesStateTo0(derivation)
derivation.newObserving = new Array(derivation.observing.length + 100)
derivation.unboundDepsCount = 0
derivation.runId = ++globalState.runId
const prevTracking = globalState.trackingDerivation
// 用 Vue 的思路来理解这句话的意义大概就是当前的 Watcher 是这个 Reaction,然后触发的 Dep 会塞到这个 Watcher 里,这里是个暂时借用,get完之后又还回去了
globalState.trackingDerivation = derivation
let result
// 这里回调f被执行掉了,触发变量的 get、获取 dep
if (globalState.disableErrorBoundaries === true) {
result = f.call(context)
} else {
try {
result = f.call(context)
} catch (e) {
result = new CaughtException(e)
}
}
globalState.trackingDerivation = prevTracking
// 建立 dep 与 derivation 的依赖关系
bindDependencies(derivation)
warnAboutDerivationWithoutDependencies(derivation)
allowStateReadsEnd(prevAllowStateReads)
return result
}
4. bindDependecies
function bindDependencies(derivation: IDerivation) {
const prevObserving = derivation.observing
const observing = (derivation.observing = derivation.newObserving!)
let lowestNewObservingDerivationState = IDerivationState.UP_TO_DATE
let i0 = 0,
l = derivation.unboundDepsCount
for (let i = 0; i < l; i++) {
const dep = observing[i]
if (dep.diffValue === 0) {
dep.diffValue = 1
if (i0 !== i) observing[i0] = dep
i0++
}
if (((dep as any) as IDerivation).dependenciesState > lowestNewObservingDerivationState) {
lowestNewObservingDerivationState = ((dep as any) as IDerivation).dependenciesState
}
}
observing.length = i0
derivation.newObserving = null
l = prevObserving.length
while (l--) {
const dep = prevObserving[l]
if (dep.diffValue === 0) {
removeObserver(dep, derivation)
}
dep.diffValue = 0
}
while (i0--) {
const dep = observing[i0]
if (dep.diffValue === 1) {
dep.diffValue = 0
// 这个就是把 dep 和 Reaction 建立联系,observable.observers.add(node),这个 node 是 Reaction 就好像把 Watcher 绑到 dep 的 target 上
addObserver(dep, derivation)
}
}
if (lowestNewObservingDerivationState !== IDerivationState.UP_TO_DATE) {
derivation.dependenciesState = lowestNewObservingDerivationState
derivation.onBecomeStale()
}
}
看到这里 derivation.observing 也就有东西了,增加了对应的 dep 依赖关系。
mobx 的依赖关系是这样的,拿 Reaction 举例 Reaction.observing 下面是对应的 dep,这层关系就是我该被谁的变动唤醒,dep 下面的 observers 就是对应的 derivation: IDerivation(Reaction 或 ComputedValue),这层关系就是,我变动了应该去通知谁做后续的响应。对应 vue 就是 Watcher 的 Deps 里面是 dep、dep 的 target 和 subs 里面是 Watcher
结论
逻辑上真的和 Vue 神似,但是给我的直观感受就是(其实我对语义化的反应很迟钝):
因为主要接触的 vue 是 js 的 vue,里面提供一大类功能的东西没有这么细的拆分,比如 IListenable 接口对应一堆类,用一个 demo 一点一点跟的时候,思路单一,感觉不到很庞大,但是有的时候,比如 observers 在我的 demo 里就对应 Reaction ,但是他也可能对应 ComputedValue ,二者之间的逻辑并不是完全一致的,都有各自的操作,虽然 vue 里面 computed 创建的 Watcher 和其他 Watcher 派发更新时的逻辑也完全不同,但是他们都是 Watcher,虽然读起来没有区别,只不过是初始化参数的差异,但是意义十分明确,就是我是 一个Watcher。
反观 ComputedValue:
class ComputedValue<T> implements IObservable, IComputedValue<T>, IDerivation
因为在派发更新的触发时 ComputedValue 和 ObservableValue 并列(这层关系没看懂有什么意义,因为 computedValue 一般都是只读的?)
所以把它塞在 ObservableObjectAdministration 的 value 下面,什么时候才能出发 set 操作?
但是在依赖收集的过程和派发更新执行时,他又和 Reaction 并列,从语义上直接读,只会感觉他和 ObservableValue 有关系,却读不出来 Reaction 和 ComputedValue 都是 IDerivation,就会感觉很蒙。