mobx 学习笔记
栏目: JavaScript · 发布时间: 5年前
内容简介:在React中,状态管理方案除了 redux 还有很多,目前工作中主要用到其中的一种—— mobx,本篇文章主要关注 mobx 的实现机制和原理。这里尝试从基本的概念开始,以问题的形式阐述。observable 对象是什么,即
在React中,状态管理方案除了 redux 还有很多,目前工作中主要用到其中的一种—— mobx,本篇文章主要关注 mobx 的实现机制和原理。
这里尝试从基本的概念开始,以问题的形式阐述。
observable 对象是什么?
observable 对象是什么,即 API observable(data)
返回了什么?
从上图来看,
-
首先我们可以确认把数据(
data
,普通的 JS 对象,API 支持 array/map/primitives 等等)转换成的 observable 对象(ob
)本身也是一个普通 JS 对象; -
其次,observable 对象(
ob
),每个属性都以 setter/getter 形式存在(数据data
的每个属性都以 getter/setter 的形式“改写”到了ob
上); -
最后,observable 对象(
ob
)上还有一个$mobx
属性,这是 mobx 魔法的核心:-
$mobx
是ObservableObjectAdministration
的实例,它通过target
属性指向对应的 observable 对象(ob
),形成循环引用; -
$mobx
的values
里 “重复” 了 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
时,其实触发的是 ObservableValue
的 get
方法。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,并建立以下依赖关系:
-
reaction.observing (数组)包含
fn
访问的所有 ObservableValue 实例; -
每个 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()
,即最终回到了同一条路径。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 【每日笔记】【Go学习笔记】2019-01-04 Codis笔记
- 【每日笔记】【Go学习笔记】2019-01-02 Codis笔记
- 【每日笔记】【Go学习笔记】2019-01-07 Codis笔记
- Golang学习笔记-调度器学习
- Vue学习笔记(二)------axios学习
- 算法/NLP/深度学习/机器学习面试笔记
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。