Hi!我是 Jet ,我准备在最近更新系列关于状态管理的源码解读(包含 redux、mobx、jotai),从平时开发中常用的 api 延展,欢迎关注
前文:
Mobx 源码解析(一):从 makeAutoObservable 出发看响应式原理
Mobx 源码解析(二):ComputedValue 初见响应式基石
Mobx 源码解析(三):看看 observer 高阶组件都做了什么👀
在上一章我们看到了 observer
高阶组件里通过创建 Reaction
对象来建立 react
组件和 mobx
的关联,这一章我们深入 Reaction
,看看他是如何做这件事的
首先回顾一下 observer
做了哪些与相关 Reaction
的操作(代码见上一章):可以看到只有两个相关的方法,一个是实例化 Reaction
,另一个是 Reaction.track
Reaction
先看 constructor
和定义
typescript
class Reaction implements IDerivation, IReactionPublic
constructor(
public name_: string = "Reaction",
private onInvalidate_: () => void,
private errorHandler_?: (error: any, derivation: IDerivation) => void,
public requiresObservable_?
) {}
Reaction
作为观察者,实现了 IDerivation
接口,实例化的时候,传递的 name_
就是组件的 name
,onInvalidate_
就是组件的 forceRender
再看一下 Reaction.track
,这里将执行 react
组件的方法传入了 track
Reaction.track
typescript
track(fn: () => void) {
if (this.isDisposed_) {
return
}
startBatch()
this.isRunning_ = true
const prevReaction = globalState.trackingContext
// trackingContext 代表当前正在运行的 reaction 或 computed value,这里赋值为当前 reaction,后续会使用
globalState.trackingContext = this
const result = trackDerivedFunction(this, fn, undefined)
globalState.trackingContext = prevReaction
this.isRunning_ = false
this.isTrackPending_ = false
// 如果当前已经被废弃,清除依赖关系
if (this.isDisposed_) {
clearObserving(this)
}
if (isCaughtException(result)) {
this.reportExceptionInDerivation_(result.cause)
}
endBatch()
}
Reaction.track
里进行的操作是开启批处理、标记 isRunning_
,然后保存自身到 globalState.trackingContext
(后续会用到),调用 trackDerivedFunction
,我们看一下这个方法
trackDerivedFunction
typescript
export function trackDerivedFunction<T>(derivation: IDerivation, f: () => T, context: any) {
const prevAllowStateReads = allowStateReadsStart(true)
// 将 derivation 置为无须更新的状态
changeDependenciesStateTo0(derivation)
// 预分配数组分配+深度变化的空间,数组将被 bindDependencies 裁剪
derivation.newObserving_ = new Array(derivation.observing_.length + 100)
derivation.unboundDepsCount_ = 0
derivation.runId_ = ++globalState.runId
const prevTracking = globalState.trackingDerivation
// trackingDerivation 表示 mobx 全局当前正在运行的派生函数
// 存储为当前的 derivation (reaction) 后续用
globalState.trackingDerivation = derivation
globalState.inBatch++
let result
if (globalState.disableErrorBoundaries === true) {
result = f.call(context)
} else {
try {
// 重点:执行 react 函数,让组件去访问 mobx 的值,就会触发 computed value 的 get 方法
// 提前说一下后续 computed value 会通过 trackingContext or trackingDerivation 来拿到当前的观察者,重点关注这两个值
result = f.call(context)
} catch (e) {
result = new CaughtException(e)
}
}
globalState.inBatch--
globalState.trackingDerivation = prevTracking
bindDependencies(derivation)
allowStateReadsEnd(prevAllowStateReads)
return result
}
trackDerivedFunction
里当前 derivation
(就是 Reaction
) 保存到 globalState.trackingDerivation
这个就是后续 computed value
用来确定是哪个观察者在观察自己的值,然后就去调用执行 react
组件,让组件去访问 mobx
的值,就会触发 computed value
的 get
方法,这时候我们回到 Computed.get
方法,不清楚 Computed
细节的的可以看我的这篇文章,回顾一下代码
Computed.get
typescript
public get(): T {
// 检测循环引用
if (this.isComputing_) {
die(32, this.name_, this.derivation)
}
// 判断当前值是否有人观察,可不可以跳过这次计算
if (
globalState.inBatch === 0 &&
this.observers_.size === 0 &&
!this.keepAlive_
) {
// 查看当前值的依赖状态,判断是否需要重新计算当前值
if (shouldCompute(this)) {
startBatch()
this.value_ = this.computeValue_(false)
endBatch()
}
} else {
// 上报有人观察改值
reportObserved(this)
// 查看当前值的依赖状态,判断是否需要重新计算当前值
if (shouldCompute(this)) {
// 当前正在运行的 reaction 或 computed value,用于确定是否当前处于响应式上下文
// 就是 reaction.track 里设置的 reaction
let prevTrackingContext = globalState.trackingContext
// 如果设置了 keepAlive_ 并且没有 prevTrackingContext,这表示当前的计算值需要保持活动状态
if (this.keepAlive_ && !prevTrackingContext) {
globalState.trackingContext = this
}
// 计算前后的值是否相等,如果不相等,发生 change
if (this.trackAndCompute()) {
// change 后通知所有观察此值的派生函数,更改他们的状态
propagateChangeConfirmed(this)
}
globalState.trackingContext = prevTrackingContext
}
}
const result = this.value_!
if (isCaughtException(result)) {
throw result.cause
}
return result
}
这时会进入到 reportObserved
函数,看一下代码
reportObserved
typescript
export function reportObserved(observable: IObservable): boolean {
// 取出当前的观察者,即之前 trackDerivedFunction 里设置的 reaction
const derivation = globalState.trackingDerivation
if (derivation !== null) {
// 检查上一次访问这个可观察对象时是否使用了相同的runId,相同代表依赖关系已经建立
if (derivation.runId_ !== observable.lastAccessedBy_) {
observable.lastAccessedBy_ = derivation.runId_
// 重点:观察者绑定可观察值,reaction 绑定了这个 computed value
derivation.newObserving_![derivation.unboundDepsCount_++] = observable
if (!observable.isBeingObserved_ && globalState.trackingContext) {
observable.isBeingObserved_ = true
// 执行有人观察的回调函数
observable.onBO()
}
}
return observable.isBeingObserved_
} else if (observable.observers_.size === 0 && globalState.inBatch > 0) {
// 用于后续检测此观察值是否会被废弃
queueForUnobservation(observable)
}
return false
}
因为之前绑定过 reaction
到 trackingDerivation
,这里可观察值 computed value
就可以知道是哪个观察者在访问自己,从而进行绑定,将当前值添加到观察者的观察列表之中,即
typescript
derivation.newObserving_![derivation.unboundDepsCount_++] = observable
这时候,就完成了观察者绑定可观察值这个过程了,我们还需要操作可观察值绑定观察者
等到 react
组件调用完成后,我们回到 trackDerivedFunction
,会调用 bindDependencies
,在这里将完成可观察值绑定观察者的操作,看一下代码
bindDependencies
typescript
// 比较 newObserving 和之前的 observing,去除重复的依赖和不需要的依赖,并绑定新添加的依赖,完成可观察值对观察者的绑定
function bindDependencies(derivation: IDerivation) {
// 先暂存当前 derivation 之前的观察值列表
const prevObserving = derivation.observing_
const observing = (derivation.observing_ = derivation.newObserving_!)
let lowestNewObservingDerivationState = IDerivationState_.UP_TO_DATE_
// 遍历 newObserving,因为可能有重复的依赖,所以使用 diffValue_ 来标记,保证每个依赖只被添加一次
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 的更新,里面的依赖都是唯一的,i0 长度之外的依赖都是无意义的了
observing.length = i0
// 置空
derivation.newObserving_ = null
// 遍历旧的依赖,查看依赖有没有被上面的过程置为 1,如果没有,说明当前 derivation 已经不再依赖这个值了,需要让可观察值去掉当前 derivation
// 然后再将之前所有的依赖 diffValue_ 置为 0
l = prevObserving.length
while (l--) {
const dep = prevObserving[l]
if (dep.diffValue_ === 0) {
// 可观察值去掉当前 derivation
removeObserver(dep, derivation)
}
dep.diffValue_ = 0
}
// 这时候已经去掉了不需要的依赖
// 这时候再去遍历新的 observing 列表,这里的列表有可能是之前就有的依赖,也有可能是新添加的依赖,之前就有的依赖(可观察值)已经绑定过当前 derivation 了,我们不需要再管,只用绑定这次新添加的
// 因为上面的循环中,我们已经将所有之前就有的依赖的 diffValue_ 置为 0 了,所以只有 diffValue_ 为 1 的依赖没有绑定当前 derivation ,给他绑定一下即可
while (i0--) {
const dep = observing[i0]
if (dep.diffValue_ === 1) {
dep.diffValue_ = 0
addObserver(dep, derivation)
}
}
// 这里完成了可观察值与观察者的绑定关系,依赖收集完成
// 在此派生计算中,一些新观察的派生可能在此过程中变为陈旧,它们没有机会传播陈旧状态,现在触发一下
if (lowestNewObservingDerivationState !== IDerivationState_.UP_TO_DATE_) {
derivation.dependenciesState_ = lowestNewObservingDerivationState
derivation.onBecomeStale_()
}
}
代码具体的操作我的注释写的很详细,里面做了三次循环,来概括一下就是:
- 第一次:将上次执行调用
react
组件收集到的依赖进行去重 - 第二次:查看之前的依赖,排除掉已经不再需要的依赖
- 第三次:查看有没有依赖没有进行绑定,如果没有,进行可观察值绑定观察者的操作
这时候,我们就已经完成了观察者与可观察值的双向绑定,mobx
完成了依赖收集
总结
总结一下绑定的过程:
react
组件创建关联reaction
- 将
reaction
保存到全局,执行react
组件访问mobx
值 - 触发
computed value
的get
方法,可观察值从全局拿到当前观察者reaction
- 观察者绑定可观察值,即
reaction.newObserving_
添加observable
react
组件执行完毕,去重和废弃无用依赖后,遍历newObserving_
让可观察值绑定观察者,即observable.observers_.add(node)
下一章我们将会讲解更新了 mobx 值之后,可观察值是如何通知观察者进行更新的