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/深度学习/机器学习面试笔记
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Masterminds of Programming
Federico Biancuzzi、Chromatic / O'Reilly Media / 2009-03-27 / USD 39.99
Description Masterminds of Programming features exclusive interviews with the creators of several historic and highly influential programming languages. Think along with Adin D. Falkoff (APL), Jame......一起来看看 《Masterminds of Programming》 这本书的介绍吧!