先看下面的代码:
class Demo {
@observable
public test = 1
log: () => void = autorun(() => {
console.log(`test input onChange: ${this.test}`)
})
}
修改test的值,会触发log函数自动执行。相当于传入autorun的方法,会自动收集依赖到的 observable值的变化。个人猜测autorun函数的工作方式是这样的
function autorun (fn) {
// 依赖收集的准备工作
fn() // 触发observable属性的get方法
// 清理依赖收集
}
下面来简单看一下autorun源码 (5.15.4)
function autorun(
view: (r: IReactionPublic) => any,
opts: IAutorunOptions = EMPTY_OBJECT
): IReactionDisposer {
const name: string = (opts && opts.name) || (view as any).name || "Autorun@" + getNextId()
const runSync = !opts.scheduler && !opts.delay
let reaction: Reaction
// 只看同步的autorun,异步是根据传入的delay setTimeout
if (runSync) {
// normal autorun
reaction = new Reaction(
name,
// reaction的onInvalidate, 用track调用reactionRunner, 也就是view(reaction), (重新)收集依赖
function(this: Reaction) {
this.track(reactionRunner)
},
opts.onError,
opts.requiresObservable
)
} else {
// ... 处理异步
}
function reactionRunner() {
view(reaction)
}
// 将 reaction 放进全局 globalState.pendingReactions 队列,里面会执行runReactions
reaction.schedule()
// 返回取消订阅
return reaction.getDisposer()
}
再来看看runReactions,runReactions是依赖收集启动方法
let reactionScheduler: (fn: () => void) => void = f => f();
function runReactions() {
// 不在事务中并且没有正在执行的reaction
if (globalState.inBatch > 0 || globalState.isRunningReactions) return
// 核心的调用runReactionsHelper
reactionScheduler(runReactionsHelper)
}
runReactionsHelper:
function runReactionsHelper() {
globalState.isRunningReactions = true
const allReactions = globalState.pendingReactions
let iterations = 0
// 遍历所有globalState.pendingReactions中的reaction,并执行每个对象的runReaction
while (allReactions.length > 0) {
if (++iterations === MAX_REACTION_ITERATIONS) {
console.error(
`Reaction doesn't converge to a stable state after ${MAX_REACTION_ITERATIONS} iterations.` +
` Probably there is a cycle in the reactive function: ${allReactions[0]}`
)
allReactions.splice(0) // clear reactions
}
let remainingReactions = allReactions.splice(0)
for (let i = 0, l = remainingReactions.length; i < l; i++)
remainingReactions[i].runReaction()
}
globalState.isRunningReactions = false
}
runReaction关键就是触发onInvalidate参数函数, 也就是用track包裹的view函数(autorun的传入函数)
// fn 就是view 函数
track(fn: () => void) {
startBatch()
....
this._isRunning = true
// trackDerivedFunction是核心, 把fn传入了trackDerivedFunction,
const result = trackDerivedFunction(this, fn, undefined)
this._isRunning = false
....
endBatch()
}
终于到trackDerivedFunction了,trackDerivedFunction就是最终调用autorun的传入函数的方法。至此就完成了触发observable属性的get方法。后面就是监听observable属性的get方法的调用, 最终完成依赖收集。例如在mobx ObservableValue类中有一个get方法,这个方法就是trap了observable属性的get方法:
public get(): T {
// reportObserved将 observable 上报给正在收集依赖的 derivation (reaction)
// derivation 从globalState.trackingDerivation中获取,globalState.trackingDerivation在上面提到的最终触发autorun的传入
// 函数 的 trackDerivedFunction中设置的
this.reportObserved()
return this.dehanceValue(this.value)
}
至此完成了依赖收集。 下面来简单的说一下obserable属性的set方法,触发set方法,如果值改变,mobx会通知此obserable属性的依赖:
public set(newValue: T) {
const oldValue = this.value
newValue = this.prepareNewValue(newValue) as any
if (newValue !== globalState.UNCHANGED) {
...
//值改变, 触发setNewValue, 最终会触发Reaction 中的onBecomeStale, 而onBecomeStale调用的就是 this.schedule(),这个就合前面的依赖收集重合了
this.setNewValue(newValue)
if (notifySpy && process.env.NODE_ENV !== "production") spyReportEnd()
}
}
最后,我们也可以看到mobx抽离Reaction这一层,设计的很巧妙,不仅抽离了依赖收集的逻辑,管理全局的依赖管理,也抽离了不同依赖管理阶段side effect,在初始化依赖收集的时候可以设置track get 方法,在依赖更新的时候也可以将Reaction用作他处,比如React,进行组件的更新。貌似Vue3抽离出的Reactivity也是类似的设计思想。