mobx 源码学习二

collect收集依赖

本文是 [mobx 源码初步认识] 第二篇

本系列文章全部采用 mobx 较新版本:[v6.2.0]

技术前提

在阅读之前,希望你对以下技术有所了解或实践,不然可能会影响你对本文的理解

  1. ES6 装饰器:decorator
  2. ES6 代理:proxy
  3. 定义对象属性:Object.defineProperty
  4. 实现简易版 观察者模式
  5. 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
}
  1. 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_
    }
}

三、收集依赖

  1. mobx中由Derivationreaction也是实现它)充当观察者,其中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有点特殊同时实现了IDerivationIObservable接口,所以它拥有上述两点的特征。因为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 用于中间关联和暂存数据

你可能感兴趣的:(mobx 源码学习二)