mobx 学习笔记

发布于 2022-04-28 12:34:34 字数 24074 浏览 1144 评论 0

在 React 中,状态管理方案除了 redux 还有很多,目前工作中主要用到其中的一种—— mobx,本篇文章主要关注 mobx 的实现机制和原理。

这里尝试从基本的概念开始,以问题的形式阐述。

observable 对象是什么?

observable 对象是什么,即 API observable(data)返回了什么?

从上图来看,

  1. 首先我们可以确认把数据(data,普通的 JS 对象,API 支持 array/map/primitives 等等)转换成的 observable 对象(ob)本身也是一个普通 JS 对象;
  2. 其次,observable 对象(ob),每个属性都以 setter/getter 形式存在(数据 data 的每个属性都以 getter/setter 的形式“改写”到了 ob 上);
  3. 最后,observable 对象(ob)上还有一个 $mobx 属性,这是 mobx 魔法的核心:
    • $mobxObservableObjectAdministration 的实例,它通过 target 属性指向对应的 observable 对象(ob),形成循环引用;
    • $mobxvalues 里 “重复” 了 observable 对象(ob)的属性,这里的 “重复” 是 mobx 魔法得以实现的重要一环。values 里的每个属性都是 ObservableValue 的实例。

到目前为止,其实我们已经可以猜测 mobx 的依赖收集和更新通知,本质上也是通过 setter/getter 。但要深入理解,必须去查看 ObservableObjectAdministration / ObservableValue 这两个类了。

ObservableObjectAdministration && extendObservable

export class ObservableObjectAdministration
    implements IInterceptable<IObjectWillChange>, IListenable {
    values: { [key: string]: ObservableValue<any> | ComputedValue<any> } = {}
    changeListeners = null
    interceptors = null

    constructor(public target: any, public name: string) {}

    /**
     * Observes this object. Triggers for the events 'add', 'update' and 'delete'.
     * See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/observe
     * for callback details
     */
    observe(callback: (changes: IObjectChange) => void, fireImmediately?: boolean): Lambda {
        invariant(
            fireImmediately !== true,
            "`observe` doesn't support the fire immediately property for observable objects."
        )
        return registerListener(this, callback)
    }

    intercept(handler): Lambda {
        return registerInterceptor(this, handler)
    }
}

ObservableObjectAdministration 比较简单,从两个关键属性 values/changeListeners 的名字就可以了解大概了。但是 ObservableObjectAdministration 本身并没有涉及到复杂的逻辑,即 values/changeListeners 怎么创建,怎么使用的都没有涉及,所以我们先回到起点,一步步来:

// 调用链:
observable(data) // 即 createObservable(data),下一步 -->
deepEnhancer(data) // 下一步 -->
observable.object(data) // 因为 data 是普通对象,所以调用 observable.object
	// observable.object 的内容:
    object<T>(props: T, name?: string): T & IObservableObject {
        if (arguments.length > 2) incorrectlyUsedAsDecorator("object")
        const res = {}
        // convert to observable object
        asObservableObject(res, name)
        // add properties
        extendObservable(res, props)
        return res as any
	}
	// asObservableObject 的内容:
	export function asObservableObject(target, name?: string): ObservableObjectAdministration {
		// 删去了一些分支
		if (!name) name = "ObservableObject@" + getNextId()

		const adm = new ObservableObjectAdministration(target, name)
		addHiddenFinalProp(target, "$mobx", adm)
		return adm
	}

上面罗列了创建一个 observable 对象(ob)的步骤,可以看到,asObservableObject 这一步添加了 $mobx 属性,但结合上面 ObservableObjectAdministration 类的定义,我们知道这一步其实并没有发生任何神奇的事,所以真正关键的在 extendObservable 里:

extendObservable(res, data) // 下一步 -->
extendObservableHelper(res, deepEnhancer, data) // 注意到 deepEnhancer 再次出现
	// extendObservableHelper 的内容:
	function extendObservableHelper(
		target: Object,
		defaultEnhancer: IEnhancer<any>,
		properties: Object[]
	): Object {
		const adm = asObservableObject(target) // 即拿到 $mobx
		const definedProps = {}
		// Note could be optimised if properties.length === 1
		for (let i = properties.length - 1; i >= 0; i--) {
			const propSet = properties[i]
			for (let key in propSet)
				if (definedProps[key] !== true && hasOwnProperty(propSet, key)) {
					definedProps[key] = true
					if ((target as any) === propSet && !isPropertyConfigurable(target, key)) continue // see #111, skip non-configurable or non-writable props for `observable(object)`.
					const descriptor = Object.getOwnPropertyDescriptor(propSet, key)
					defineObservablePropertyFromDescriptor(adm, key, descriptor!, defaultEnhancer)
				}
		}
		return target
	}
	// defineObservablePropertyFromDescriptor 的内容:
	export function defineObservablePropertyFromDescriptor(
		adm: ObservableObjectAdministration,
		propName: string,
		descriptor: PropertyDescriptor,
		defaultEnhancer: IEnhancer<any>
	) {
		if (adm.values[propName] && !isComputedValue(adm.values[propName])) {
			// already observable property
			adm.target[propName] = descriptor.value // the property setter will make 'value' reactive if needed.
			return
		}

		// not yet observable property
		if ("value" in descriptor) {
			// 省略一些其它分支
			if (isComputedValue(descriptor.value)) {
				// x: computed(someExpr)
				defineComputedPropertyFromComputedValue(adm, propName, descriptor.value)
			} else {
				// x: someValue
				defineObservableProperty(adm, propName, descriptor.value, defaultEnhancer)
			}
		} else {}
	}
	// defineObservableProperty 的内容:
	export function defineObservableProperty(
		adm: ObservableObjectAdministration,
		propName: string,
		newValue,
		enhancer: IEnhancer<any>
	) {
		// 省略一些代码
		const observable = (adm.values[propName] = new ObservableValue(
			newValue,
			enhancer,
			`${adm.name}.${propName}`,
			false
		))
		newValue = (observable as any).value // observableValue might have changed it
		// adm.target 即我们的 observable 对象: ob
		Object.defineProperty(adm.target, propName, generateObservablePropConfig(propName))
		notifyPropertyAddition(adm, adm.target, propName, newValue)
	}
	// generateObservablePropConfig 的内容:
	export function generateObservablePropConfig(propName) {
		return (
			observablePropertyConfigs[propName] ||
			(observablePropertyConfigs[propName] = {
				configurable: true,
				enumerable: true,
				get: function() {
					return this.$mobx.values[propName].get()
				},
				set: function(v) {
					// 在下面一小节贴代码
					setPropertyValue(this, propName, v)
				}
			})
		)
	}

跟随上面一步步追下来,我们最终可知道,当我们访问 ob.name 时,其实触发的是 ObservableValueget 方法。observable 对象(ob)上的属性(和 data 同名的那些)基本只是一个包装,处理逻辑都在 ObservableValue 里,包括依赖收集和更新通知。

ObservableValue

// 删除了 interceptors 相关的及一些暂时不用了解的方法
export class ObservableValue<T> extends BaseAtom
    implements IObservableValue<T>, IInterceptable<IValueWillChange<T>>, IListenable {
    hasUnreportedChange = false
    interceptors
    changeListeners
    protected value
    dehancer: any = undefined

    constructor(
        value: T,
        protected enhancer: IEnhancer<T>,
        name = "ObservableValue@" + getNextId(),
        notifySpy = true
    ) {
		super(name)
		// 这里传进来的 enhancer 是 deepEnhancer,所以,我们在递归处理:
		// 我们将把 value 变成 observable 对象!
        this.value = enhancer(value, undefined, name)
    }

    private dehanceValue(value: T): T {
        if (this.dehancer !== undefined) return this.dehancer(value)
        return value
    }

    public set(newValue: T) {
        const oldValue = this.value
        newValue = this.prepareNewValue(newValue) as any
        if (newValue !== UNCHANGED) {
            const notifySpy = isSpyEnabled()
            if (notifySpy) {
                spyReportStart({
                    type: "update",
                    object: this,
                    newValue,
                    oldValue
                })
            }
            this.setNewValue(newValue)
            if (notifySpy) spyReportEnd()
        }
    }

    private prepareNewValue(newValue): T | IUNCHANGED {
        checkIfStateModificationsAreAllowed(this)
        // apply modifier
        newValue = this.enhancer(newValue, this.value, this.name)
        return this.value !== newValue ? newValue : UNCHANGED
    }

    setNewValue(newValue: T) {
        const oldValue = this.value
        this.value = newValue
        this.reportChanged()
        if (hasListeners(this)) {
            notifyListeners(this, {
                type: "update",
                object: this,
                newValue,
                oldValue
            })
        }
    }

    public get(): T {
        this.reportObserved()
        return this.dehanceValue(this.value)
    }
}

export function setPropertyValue(instance, name: string, newValue) {
    const adm = instance.$mobx
    const observable = adm.values[name]

    newValue = observable.prepareNewValue(newValue)

    // notify spy & observers
    if (newValue !== UNCHANGED) {
        const notify = hasListeners(adm)
        const change =
            notify || notifySpy
                ? {
                      type: "update",
                      object: instance,
                      oldValue: (observable as any).value,
                      name,
                      newValue
                  }
                : null

        observable.setNewValue(newValue)
        if (notify) notifyListeners(adm, change)
    }
}

从代码来看,我们已经看到关键的 get/set 函数了,看到 reportObserved/notifyListeners 等预期的东西了。

依赖收集怎么完成的?

通过上面的代码,我们已经知道当我们访问一个属性时的调用链路了,那么,依赖是怎么收集的呢(最终一步reportObserved)?

// BaseAtom 的方法
public reportObserved() {
	reportObserved(this) // this 是 BaseAtom/ObservableValue 的实例
}

export function reportObserved(observable: IObservable) {
	// 在直接访问 ob.name 时,我们发现 derivation 是空的,最终并没有收集依赖。
	// 然后联系 mobx 实践,我们需要引入 derivation 的概念
    const derivation = globalState.trackingDerivation
    if (derivation !== null) {
		// 而当我们通过 autorun 等 API 访问 ob 的属性时,globalState.trackingDerivation 是会被设置为当前 derivation 的,
		// 然后 observable/observablevalue 就会被添加到 derivation.newObserving
        if (derivation.runId !== observable.lastAccessedBy) {
            observable.lastAccessedBy = derivation.runId
            derivation.newObserving![derivation.unboundDepsCount++] = observable
        }
    } else if (observable.observers.length === 0) {
        queueForUnobservation(observable)
    }
}

autorun & Reaction & trackDerivedFunction

export function autorun(
    name: string,
    view: (r: IReactionPublic) => any,
    scope?: any
): IReactionDisposer
export function autorun(arg1: any, arg2: any, arg3?: any) {
    let name: string, view: (r: IReactionPublic) => any, scope: any

    if (typeof arg1 === "string") {
        name = arg1
        view = arg2
        scope = arg3
    } else {
		// 通常调用 autorun(() => console.log('hi', ob.name)) 时 arg1 是函数
        name = arg1.name || "Autorun@" + getNextId()
        view = arg1
        scope = arg2
    }

    invariant(typeof view === "function", getMessage("m004"))
    invariant(isAction(view) === false, getMessage("m005"))
    if (scope) view = view.bind(scope)

    const reaction = new Reaction(name, function() {
        this.track(reactionRunner)
    })

    function reactionRunner() {
        view(reaction)
    }

    reaction.schedule() // 开始一个 reaction

    return reaction.getDisposer()
}

export class Reaction implements IDerivation, IReactionPublic {
    observing: IObservable[] = [] // nodes we are looking at. Our value depends on these nodes
    newObserving: IObservable[] = []
    dependenciesState = IDerivationState.NOT_TRACKING
    diffValue = 0
    runId = 0
    unboundDepsCount = 0
    __mapid = "#" + getNextId()
    isDisposed = false
    _isScheduled = false
    _isTrackPending = false
    _isRunning = false
    errorHandler: (error: any, derivation: IDerivation) => void

    constructor(
        public name: string = "Reaction@" + getNextId(),
        private onInvalidate: () => void
    ) {}

    onBecomeStale() {
        this.schedule()
    }

	// 开始一个 reaction
    schedule() {
        if (!this._isScheduled) {
			this._isScheduled = true
			// 放到全局的 pendingReactions 队列中
			globalState.pendingReactions.push(this)
			// 然后开始处理所有的 pendingReactions(最终是执行自己的runReaction)
            runReactions()
        }
    }

    isScheduled() {
        return this._isScheduled
    }

    /**
     * internal, use schedule() if you intend to kick off a reaction
     */
    runReaction() { // schedule 最终调用此方法
        if (!this.isDisposed) { // reaction 还存活
            startBatch() // 即 globalState.inBatch++
			this._isScheduled = false
			// 检查 reaction.dependenciesState 来决定,是否需要重新计算
			// 第一次时,state是 NOT_TRACKING,需要计算。
            if (shouldCompute(this)) {
                this._isTrackPending = true
				// 执行此函数,autorun 中,这个函数会调用 this.track
                this.onInvalidate()
            }
            endBatch()
        }
    }
	// 依赖收集,删掉了spyReport相关代码
    track(fn: () => void) {
        startBatch()
        this._isRunning = true
        const result = trackDerivedFunction(this, fn, undefined)
        this._isRunning = false
        this._isTrackPending = false
        if (this.isDisposed) {
            // disposed during last run. Clean up everything that was bound after the dispose call.
            clearObserving(this)
        }
        if (isCaughtException(result)) this.reportExceptionInDerivation(result.cause)
        endBatch()
	}
	// 删除了一些方法
}


/**
 * Executes the provided function `f` and tracks which observables are being accessed.
 * The tracking information is stored on the `derivation` object and the derivation is registered
 * as observer of any of the accessed observables.
 */
export function trackDerivedFunction<T>(derivation: IDerivation, f: () => T, context) {
    // 把 derivation.dependenciesState 和 derivation.observing数组内所有 ob.lowestObserverState 改为 IDerivationState.UP_TO_DATE (0)
	changeDependenciesStateTo0(derivation)
	// 提前为新 observing 申请空间,之后会trim
    derivation.newObserving = new Array(derivation.observing.length + 100)
    derivation.unboundDepsCount = 0
    derivation.runId = ++globalState.runId
	const prevTracking = globalState.trackingDerivation
	// 设置 globalState.trackingDerivation 为当前 derivation(autorun创建的reaction)
	globalState.trackingDerivation = derivation
	// 初始化工作完毕后执行函数 fn 来追踪哪些 observables 被访问了。
    let result
    try {
		// 这一步将会触发 observable 的访问,即我们 ob.name --> $mobx.name.get() (ObservableValue.prototype.get)-->
		// reportObserved(ObservableValue) !
		// 很棒,我们上边一步步分析的最终被使用和验证了。
        result = f.call(context)
    } catch (e) {
        result = new CaughtException(e)
    }
	globalState.trackingDerivation = prevTracking
	// 到这一步时,虽然 derivation.newObserving[0] 被设置为了 ob.name 对应的 ObservableValue 实例,但真正的绑定处理在下面的 bindDependencies
    bindDependencies(derivation)
    return result
}

/**
 * diffs newObserving with observing.
 * update observing to be newObserving with unique observables
 * notify observers that become observed/unobserved
 */
function bindDependencies(derivation: IDerivation) {
    const prevObserving = derivation.observing
    const observing = (derivation.observing = derivation.newObserving!)
    let lowestNewObservingDerivationState = IDerivationState.UP_TO_DATE


    // Go through all new observables and check diffValue: (this list can contain duplicates):
    //   0: first occurrence, change to 1 and keep it
    //   1: extra occurrence, drop it
    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++
        }

        // Upcast is 'safe' here, because if dep is IObservable, `dependenciesState` will be undefined,
        // not hitting the condition
        if (((dep as any) as IDerivation).dependenciesState > lowestNewObservingDerivationState) {
			lowestNewObservingDerivationState = ((dep as any) as IDerivation).dependenciesState
        }
    }
    observing.length = i0

	derivation.newObserving = null // newObserving shouldn't be needed outside tracking (statement moved down to work around FF bug, see #614)

    // Go through all old observables and check diffValue: (it is unique after last bindDependencies)
    //   0: it's not in new observables, unobserve it
    //   1: it keeps being observed, don't want to notify it. change to 0
    l = prevObserving.length
    while (l--) {
        const dep = prevObserving[l]
        if (dep.diffValue === 0) {
            removeObserver(dep, derivation)
        }
        dep.diffValue = 0
    }

    // Go through all new observables and check diffValue: (now it should be unique)
    //   0: it was set to 0 in last loop. don't need to do anything.
    //   1: it wasn't observed, let's observe it. set back to 0
    while (i0--) {
        const dep = observing[i0]
        if (dep.diffValue === 1) {
            dep.diffValue = 0
            addObserver(dep, derivation)
        }
    }

    // Some new observed derivations may become stale during this derivation computation
    // so they have had no chance to propagate staleness (#916)
    if (lowestNewObservingDerivationState !== IDerivationState.UP_TO_DATE) {
        derivation.dependenciesState = lowestNewObservingDerivationState
        derivation.onBecomeStale()
    }
}

const MAX_REACTION_ITERATIONS = 100

// 有点奇怪的工具函数:接受一个函数并执行它
let reactionScheduler: (fn: () => void) => void = f => f()

export function runReactions() {
	// 如果正在执行 runReactions (globalState.isRunningReactions = true),
	// 那么直接返回。因为 runReactionsHelper 最终会处理完所有的 pendingReactions,
	// 只要 push reaction 到 pendingReactions 就可以了。
    if (globalState.inBatch > 0 || globalState.isRunningReactions) return
    reactionScheduler(runReactionsHelper)
}

function runReactionsHelper() {
    globalState.isRunningReactions = true
    const allReactions = globalState.pendingReactions
    let iterations = 0

	// 当执行 reactions,可能有新的 reaction 触发(看上面);这里使用
	// 2 个变量(allReactions/remainingReactions)来检查经过一段时间后(其实是100次迭代)
	// 剩余 reactions 是否已经为空;不空则认为可能代码有问题(有 cycle reaction)。
    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() // 就是执行 reaction 自己的 runReaction 方法
    }
    globalState.isRunningReactions = false
}

结论:

autorun(fn) 执行后(fn访问 observable),创建了一个 reaction,并建立以下依赖关系:

  1. reaction.observing (数组)包含 fn访问的所有 ObservableValue 实例;
  2. 每个 ObservableValue 实例的属性 observers (数组)包含 reaction。

更新通知

当我们更新 ob.name 时,这个变化怎么通知到依赖于 ob.name 的我们的 reaction ?

相关代码其实之前已经贴出:

// ObservableValue
setNewValue(newValue: T) {
	const oldValue = this.value
	this.value = newValue
	this.reportChanged()
	if (hasListeners(this)) {
		notifyListeners(this, {
			type: "update",
			object: this,
			newValue,
			oldValue
		})
	}
}

不过原本以为通知是 notifyListeners 触发的,实际上,其实是 reportChanged 来触发。这可能出乎意料,但考虑到 reportObservabled 用于收集依赖,reportChanged 显得对称而合理。

/**
 * Invoke this method _after_ this method has changed to signal mobx that all its observers should invalidate.
 */
public reportChanged() {
	startBatch()
	propagateChanged(this)
	endBatch()
}


// Called by Atom when its value changes
export function propagateChanged(observable: IObservable) {
    // invariantLOS(observable, "changed start");
    if (observable.lowestObserverState === IDerivationState.STALE) return
    observable.lowestObserverState = IDerivationState.STALE

    const observers = observable.observers
    let i = observers.length
    while (i--) {
        const d = observers[i]
        if (d.dependenciesState === IDerivationState.UP_TO_DATE) d.onBecomeStale()
        d.dependenciesState = IDerivationState.STALE
    }
    // invariantLOS(observable, "changed end");
}

如上,最终其实是执行了 reaction.onBecomeStale() --> reaction.schedule(),即最终回到了同一条路径。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

JSmiles

生命进入颠沛而奔忙的本质状态,并将以不断告别和相遇的陈旧方式继续下去。

0 文章
0 评论
84961 人气
更多

推荐作者

尘世孤行

文章 0 评论 0

烟─花易冷

文章 0 评论 0

倒带

文章 0 评论 0

忱杏

文章 0 评论 0

送君千里

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文