Hi!我是 Jet ,我准备在最近更新系列关于状态管理的源码解读(包含 redux、mobx、jotai),从平时开发中常用的 api 延展,欢迎关注
前文:
Mobx 源码解析(一):从 makeAutoObservable 出发看响应式原理
上文我们从 makeAutoObservable
看到 mobx
实现响应式的过程中,将原有对象的属性删除了,替代的是实例化了一个 ComputedValue
对象,保存到了 ObservableObjectAdministration
上的 values_
: map
对象上,并将其 get
访问代理成为了 ObservableObjectAdministration.getObservablePropValue_
,即 ComputedValue.get
,这一章我们看一下 ComputedValue
的源码实现,看看其内部做了哪些事
ComputedValue
typescript
class ComputedValue<T> implements IObservable, IComputedValue<T>, IDerivation
首先是其类的定义,实现了 IObservable
, IComputedValue
, IDerivation
这三个接口,IComputedValue
只定义了实现 get、set 方法,看一下另外两个接口的定义
IObservable
typescript
export interface IObservable extends IDepTreeNode {
// 当 observable 的值发生变化时,diffValue_ 会增加,用于在比较过程中检测是否发生了变化,用于避免不必要的重新计算
diffValue_: number
// 用于确定最后一次访问此 observable 的派生函数的标识符。如果最后一次访问的派生函数与当前派生函数相同,说明依赖关系已经建立
lastAccessedBy_: number
// 标识此 observable 是否正在被观察
isBeingObserved_: boolean
// 用于记录可观察值的依赖状态,以避免不必要的状态传播
lowestObserverState_: IDerivationState_
// 标识是否处于待观察状态,确保在每个批处理中只向 global.pendingUnobservations 推送一次自身
isPendingUnobservation_: boolean
// 存储观察此 observable 的派生函数
observers_: Set<IDerivation>
// 在被取消观察时的回调函数
onBUO(): void
// 在被观察时的回调函数
onBO(): void
// 分别存储在取消观察前和观察时需要调用的回调函数集合,供调用
onBUOL: Set<Lambda> | undefined
onBOL: Set<Lambda> | undefined
}
IObservable
作为可观察值定义的基石,定义了作为可观察值的必要因素,如一些标记值、优化用的标识、回调函数和存储 observers_
的观察者,可以看到要求使用 IDerivation
的实现,再看一下 IDerivation
定义了哪些
IDerivation
typescript
export interface IDerivation extends IDepTreeNode {
// 存储此派生函数当前正在观察的 observable 对象,他们的变化会触发派生函数重新计算
observing_: IObservable[]
// 临时记录新的观察对象。计算完成后,将其复制到 observing_ 中。
newObserving_: null | IObservable[]
// 用于记录派生函数的依赖状态,以确定是否需要重新计算。具体的依赖状态包括是否变为脏状态等
dependenciesState_: IDerivationState_
// 表示当前派生函数的运行标识符,每次追踪时递增
runId_: number
// 记录在当前运行中派生函数未绑定的依赖数量。
unboundDepsCount_: number
// 当派生函数变为陈旧状态时,即需要重新计算时,会调用此回调函数。用于执行一些清理和通知操作
onBecomeStale_(): void
// 当前是否处于追踪模式
isTracing_: TraceMode
// for debug,如果派生函数没有依赖项,是否发出警告
requiresObservable_?: boolean
}
而 IDerivation
作为观察者定义的基石,同样定义了作为观察者的必要因素,如一些状态标记、回调函数和存储 observing_
的观察值
从而得知,ComputedValue
不仅仅实现了某一方功能,既是观察者又是被观察者,所以它的成员变量是可观察变量和衍生的集合。
除了这些,ComputedValue
还实现了一些其他属性,如下
typescript
// 表示是否正在计算,用来检查循环引用
isComputing_: boolean = false
// 是否正在 set
isRunningSetter_: boolean = false
// 重点:保存值,受保护
protected value_: T | undefined | CaughtException = new CaughtException(null)
// get 函数,就是 ObservableObjectAdministration.getObservablePropValue_
derivation: () => T
// set 函数
setter_?: (value: T) => void
// target 上下文对象
scope_: Object | undefined
// 比较逻辑,是浅比较还是深比较
private equals_: IEqualsComparer<any>
// 当设置为真时,计算值将会一直保持活跃状态,而不会因为没有观察者而自动清除
keepAlive_: boolean
constructor(options: IComputedValueOptions<T>) {
if (!options.get) {
die(31)
}
this.derivation = options.get!
this.name_ = options.name || "ComputedValue"
if (options.set) {
this.setter_ = createAction(
"ComputedValue-setter",
options.set
) as any
}
// 配置浅比较还是深比较
this.equals_ =
options.equals ||
((options as any).compareStructural || (options as any).struct
? comparer.structural
: comparer.default)
this.scope_ = options.context
this.keepAlive_ = !!options.keepAlive
}
接着看一下 ComputedValue
的 get
方法,看一下当有观察者观察 ComputedValue
时(就是获取 ComputedValue
的值,即 get
)发生了什么,这也是 mobx
能够自动进行依赖收集的关键
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,用于确定是否当前处于响应式上下文
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
}
这里面有几个关键的函数
shouldCompute
computeValue_
reportObserved
trackAndCompute
propagateChangeConfirmed
在代码注释里,我只是概括了一下他们所做的事情,在后续章节中将会详细做源码解读。
下一章我们将从组件出发,看一下 react
是如何调用到 ComputedValue.get
然后绑定好依赖关系的