mobx 学习笔记

栏目: JavaScript · 发布时间: 5年前

内容简介:在React中,状态管理方案除了 redux 还有很多,目前工作中主要用到其中的一种—— mobx,本篇文章主要关注 mobx 的实现机制和原理。这里尝试从基本的概念开始,以问题的形式阐述。observable 对象是什么,即

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

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

observable 对象是什么?

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

mobx 学习笔记

从上图来看,

  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)
    }
}

mobx 学习笔记

autorun & Reaction & trackDerivedFunction

mobx 学习笔记

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()
    }
}

mobx 学习笔记

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。

mobx 学习笔记

更新通知

当我们更新 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() ,即最终回到了同一条路径。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

大演算

大演算

佩德羅.多明戈斯 / 張正苓,胡玉城 / 三采 / 2016-8-1 / 620

揭開大數據、人工智慧、機器學習的祕密, 打造人類文明史上最強大的科技——終極演算法! 有一個終極演算法,可以解開宇宙所有的祕密, 現在大家都在競爭,誰能最先解開它! .機器學習是什麼?大演算又是什麼? .大演算如何運作與發展,機器可以預測什麼? .我們可以信任機器學過的東西嗎? .商業、政治為什麼要擁抱機器學習? .不只商業與政治,醫學與科學界也亟需......一起来看看 《大演算》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

URL 编码/解码
URL 编码/解码

URL 编码/解码