collect收集依赖
本文是 [mobx 源码初步认识] 第二篇
本系列文章全部采用 mobx 较新版本:[v6.2.0]
技术前提
在阅读之前,希望你对以下技术有所了解或实践,不然可能会影响你对本文的理解
- ES6 装饰器:decorator
- ES6 代理:proxy
- 定义对象属性:Object.defineProperty
- 实现简易版 观察者模式
- mobx中observable-object和observable-value包装后的结果有所了解(回顾上次mbox构建object)
准备
一、目录结构
├── src
│ ├── api // 进行劫持方法和作出反应的 api
│ ├── core // 全局状态、函数等
│ ├── types // 重写关于 object,array 等类型的 api
│ ├── utils // 工具方法
│ ├── internal.ts
│ └── mobx.ts // 全局导出
└── package.json
二、通过构建后的ObservableValue 对象
1.ObservableValue
ObservableValue充当可观察对象,其中 observers
代表依赖它的观察者集合,lowestObserverState
代表可观察对象的状态(全文的 observable 代表 observableValue,observableObject,observableArray...)
export interface IObservable extends IDepTreeNode {
diffValue_: number
lastAccessedBy_: number
isBeingObserved_: boolean
lowestObserverState_: IDerivationState_ // Used to avoid redundant propagations
isPendingUnobservation_: boolean // Used to push itself to global.pendingUnobservations at most once per batch.
observers_: Set // 使用set去重
onBUO(): void
onBO(): void
onBUOL: Set | undefined
onBOL: Set | undefined
}
- Atom
export class Atom implements IAtom {
isPendingUnobservation_ = false // for effective unobserving. BaseAtom has true, for extra optimization, so its onBecomeUnobserved never gets called, because it's not needed
isBeingObserved_ = false
observers_ = new Set()
diffValue_ = 0
lastAccessedBy_ = 0
lowestObserverState_ = IDerivationState_.NOT_TRACKING_
constructor(public name_ = __DEV__ ? "Atom@" + getNextId() : "Atom") {}
public onBOL: Set | undefined
public onBUOL: Set | undefined
public onBO() {
if (this.onBOL) {
this.onBOL.forEach(listener => listener())
}
}
public onBUO() {
if (this.onBUOL) {
this.onBUOL.forEach(listener => listener())
}
}
// 通过get时 收集依赖调用
public reportObserved(): boolean {
return reportObserved(this)
}
// 通过set是 触发依赖更新
public reportChanged() {
startBatch()
propagateChanged(this)
endBatch()
}
toString() {
return this.name_
}
}
三、收集依赖
- mobx
中由
Derivation(
reaction也是实现它)充当观察者,其中
observing代表依赖的可观察对象集合,
dependenciesState` 代表观察者状态
export interface IDerivation extends IDepTreeNode {
observing_: IObservable[] // 依赖的可观察对象集合
newObserving_: null | IObservable[] //
dependenciesState_: IDerivationState_ // 观察者状态
runId_: number
unboundDepsCount_: number
onBecomeStale_(): void
isTracing_: TraceMode
requiresObservable_?: boolean
}
2.Reaction 对象
export class Reaction implements IDerivation, IReactionPublic {
observing_: IObservable[] = []
newObserving_: IObservable[] = []
dependenciesState_ = IDerivationState_.NOT_TRACKING_
diffValue_ = 0
runId_ = 0
unboundDepsCount_ = 0
isDisposed_ = false
isScheduled_ = false
isTrackPending_ = false
isRunning_ = false
isTracing_: TraceMode = TraceMode.NONE
constructor(
public name_: string = __DEV__ ? "Reaction@" + getNextId() : "Reaction",
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()
}
}
runReaction_() {
/*xxxxx*/
}
// 依赖收集
track(fn: () => void) {
/*xxxxx*/
const prevReaction = globalState.trackingContext
globalState.trackingContext = this
const result = trackDerivedFunction(this, fn, undefined)
globalState.trackingContext = prevReaction
}
reportExceptionInDerivation_(error: any) {
}
dispose() {
if (!this.isDisposed_) {
this.isDisposed_ = true
if (!this.isRunning_) {
startBatch()
clearObserving(this)
endBatch()
}
}
}
getDisposer_(): IReactionDisposer {
const r = this.dispose.bind(this) as IReactionDisposer
r[$mobx] = this
return r
}
toString() {
return `Reaction[${this.name_}]`
}
trace(enterBreakPoint: boolean = false) {
trace(this, enterBreakPoint)
}
}
3,trackDerivedFunction发起收集依赖和绑定依赖
先调用 changeDependenciesStateTo0 方法将 derivation 和 observing 置为稳定态 UP_TO_DATE,主要是方便后续判断是否处在收集依赖阶段(下章会讲)
trackingDerivation 为在全局正在收集依赖的 derivation,初始为 null
使用 newObserving 临时变量储存依赖和为后面的绑定依赖做准备
然后是“切分支”,见下代码,后面会多次用到
其主要目的是保存正在收集依赖的 derivation,以防 f.call(context) 时递归调用了 trackDerivedFunction,而找不到上下文
聪明的你可能猜到了其中包含 ComputedValue
f.call(context) 正式执行 autorun 中传入的函数,newObserving 数组也是在这一阶段填充的
最后是 bindDependencies 绑定依赖
export function trackDerivedFunction(derivation: IDerivation, f: () => T, context: any) {
const prevAllowStateReads = allowStateReadsStart(true)
// pre allocate array allocation + room for variation in deps
// array will be trimmed by bindDependencies
changeDependenciesStateTo0(derivation)
derivation.newObserving_ = new Array(derivation.observing_.length + 100)
derivation.unboundDepsCount_ = 0
derivation.runId_ = ++globalState.runId
const prevTracking = globalState.trackingDerivation
globalState.trackingDerivation = derivation
globalState.inBatch++
let result
if (globalState.disableErrorBoundaries === true) {
result = f.call(context)
} else {
try {
result = f.call(context)
} catch (e) {
result = new CaughtException(e)
}
}
globalState.inBatch--
globalState.trackingDerivation = prevTracking
bindDependencies(derivation)
warnAboutDerivationWithoutDependencies(derivation)
allowStateReadsEnd(prevAllowStateReads)
return result
}
4,依赖收集,还记得之前说过的构建对象object后的拦截属性 get,set拦截属性方法吗?对了,我们在获取一个对象属性的时候语法:
{obj.A} // 此方法会被 proxy中的get拦截,并且上报依赖该值的对象
我们看上报的方法
其中observable 是通过装饰器构建后的对象,包括object,array,value.map,set 等
把我们构建的对象收集到 globalState.trackingDerivation中
export function reportObserved(observable: IObservable): boolean {
checkIfStateReadsAreAllowed(observable)
const derivation = globalState.trackingDerivation
if (derivation !== null) {
if (derivation.runId_ !== observable.lastAccessedBy_) {
observable.lastAccessedBy_ = derivation.runId_
// Tried storing newObserving, or observing, or both as Set, but performance didn't come close...
derivation.newObserving_![derivation.unboundDepsCount_++] = observable
if (!observable.isBeingObserved_ && globalState.trackingContext) {
observable.isBeingObserved_ = true
observable.onBO()
}
}
return true
} else if (observable.observers_.size === 0 && globalState.inBatch > 0) {
queueForUnobservation(observable)
}
return false
}
5,修改更新,当然修改数据后出发更新需要在proxy拦截属性set时收集改变
export function propagateChanged(observable: IObservable) {
// invariantLOS(observable, "changed start");
if (observable.lowestObserverState_ === IDerivationState_.STALE_) return
observable.lowestObserverState_ = IDerivationState_.STALE_
// Ideally we use for..of here, but the downcompiled version is really slow...
observable.observers_.forEach(d => {
if (d.dependenciesState_ === IDerivationState_.UP_TO_DATE_) {
if (__DEV__ && d.isTracing_ !== TraceMode.NONE) {
logTraceInfo(d, observable)
}
d.onBecomeStale_()
}
d.dependenciesState_ = IDerivationState_.STALE_
})
// invariantLOS(observable, "changed end");
}
四.computedValue
ComputedValue有点特殊同时实现了
IDerivation和
IObservable接口,所以它拥有上述两点的特征。因为
ComputedValue可以被
Derivation依赖,同时也可以依赖
ObservableValue`
export class ComputedValue implements IObservable, IComputedValue, IDerivation {
dependenciesState_ = IDerivationState_.NOT_TRACKING_
observing_: IObservable[] = [] // nodes we are looking at. Our value depends on these nodes
newObserving_ = null // during tracking it's an array with new observed observers
isBeingObserved_ = false
isPendingUnobservation_: boolean = false
observers_ = new Set()
diffValue_ = 0
runId_ = 0
lastAccessedBy_ = 0
lowestObserverState_ = IDerivationState_.UP_TO_DATE_
unboundDepsCount_ = 0
protected value_: T | undefined | CaughtException = new CaughtException(null)
name_: string
triggeredBy_?: string
isComputing_: boolean = false // to check for cycles
isRunningSetter_: boolean = false
derivation: () => T // N.B: unminified as it is used by MST
setter_?: (value: T) => void
isTracing_: TraceMode = TraceMode.NONE
scope_: Object | undefined
private equals_: IEqualsComparer
private requiresReaction_: boolean
keepAlive_: boolean
constructor(options: IComputedValueOptions) {
if (!options.get) die(31)
this.derivation = options.get!
this.name_ = options.name || (__DEV__ ? "ComputedValue@" + getNextId() : "ComputedValue")
if (options.set) {
this.setter_ = createAction(
__DEV__ ? this.name_ + "-setter" : "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.requiresReaction_ = !!options.requiresReaction
this.keepAlive_ = !!options.keepAlive
}
onBecomeStale_() {
propagateMaybeChanged(this)
}
public onBOL: Set | undefined
public onBUOL: Set | undefined
public onBO() {
if (this.onBOL) {
this.onBOL.forEach(listener => listener())
}
}
public onBUO() {
if (this.onBUOL) {
this.onBUOL.forEach(listener => listener())
}
}
public get(): T {
if (this.isComputing_) die(32, this.name_, this.derivation)
if (
globalState.inBatch === 0 &&
// !globalState.trackingDerivatpion &&
this.observers_.size === 0 &&
!this.keepAlive_
) {
if (shouldCompute(this)) {
this.warnAboutUntrackedRead_()
startBatch() // See perf test 'computed memoization'
this.value_ = this.computeValue_(false)
endBatch()
}
} else {
reportObserved(this)
if (shouldCompute(this)) {
let prevTrackingContext = globalState.trackingContext
if (this.keepAlive_ && !prevTrackingContext) globalState.trackingContext = this
if (this.trackAndCompute()) propagateChangeConfirmed(this)
globalState.trackingContext = prevTrackingContext
}
}
const result = this.value_!
if (isCaughtException(result)) throw result.cause
return result
}
public set(value: T) {
if (this.setter_) {
if (this.isRunningSetter_) die(33, this.name_)
this.isRunningSetter_ = true
try {
this.setter_.call(this.scope_, value)
} finally {
this.isRunningSetter_ = false
}
} else die(34, this.name_)
}
trackAndCompute(): boolean {
// N.B: unminified as it is used by MST
const oldValue = this.value_
const wasSuspended =
/* see #1208 */ this.dependenciesState_ === IDerivationState_.NOT_TRACKING_
const newValue = this.computeValue_(true)
const changed =
wasSuspended ||
isCaughtException(oldValue) ||
isCaughtException(newValue) ||
!this.equals_(oldValue, newValue)
if (changed) {
this.value_ = newValue
}
return changed
}
computeValue_(track: boolean) {
this.isComputing_ = true
// don't allow state changes during computation
const prev = allowStateChangesStart(false)
let res: T | CaughtException
if (track) {
res = trackDerivedFunction(this, this.derivation, this.scope_)
} else {
if (globalState.disableErrorBoundaries === true) {
res = this.derivation.call(this.scope_)
} else {
try {
res = this.derivation.call(this.scope_)
} catch (e) {
res = new CaughtException(e)
}
}
}
allowStateChangesEnd(prev)
this.isComputing_ = false
return res
}
suspend_() {
if (!this.keepAlive_) {
clearObserving(this)
this.value_ = undefined // don't hold on to computed value!
}
}
observe_(listener: (change: IComputedDidChange) => void, fireImmediately?: boolean): Lambda {
let firstTime = true
let prevValue: T | undefined = undefined
return autorun(() => {
// TODO: why is this in a different place than the spyReport() function? in all other observables it's called in the same place
let newValue = this.get()
if (!firstTime || fireImmediately) {
const prevU = untrackedStart()
listener({
observableKind: "computed",
debugObjectName: this.name_,
type: UPDATE,
object: this,
newValue,
oldValue: prevValue
})
untrackedEnd(prevU)
}
firstTime = false
prevValue = newValue
})
}
warnAboutUntrackedRead_() {
if (!__DEV__) return
if (this.requiresReaction_ === true) {
die(`[mobx] Computed value ${this.name_} is read outside a reactive context`)
}
if (this.isTracing_ !== TraceMode.NONE) {
console.log(
`[mobx.trace] '${this.name_}' is being read outside a reactive context. Doing a full recompute`
)
}
if (globalState.computedRequiresReaction) {
console.warn(
`[mobx] Computed value ${this.name_} is being read outside a reactive context. Doing a full recompute`
)
}
}
toString() {
return `${this.name_}[${this.derivation.toString()}]`
}
valueOf(): T {
return toPrimitive(this.get())
}
[Symbol.toPrimitive]() {
return this.valueOf()
}
}
五、整体步骤
1,创建reaction 对象
2,将获取observablevalue属性和值的方法传入到 reaction.track中,
3,调用reaction.track中trackDerivedFunction
4,在trackDerivedFunction 通过传入的f.call()发起收集依赖
5,调用observablevalue中的get方法,
6,通过Atom 中的 reportObserved方法将 observablevalue对象收集到 reaction.newObserving_队列中
7,使用绑定依赖方法 bindDependencies(derivation: IDerivation)将 创建的对象那个 reaction绑定到observablevalue对象的observers中
8, globalState.trackingDerivation 用于中间关联和暂存数据