You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
exportclassObservableObjectAdministrationimplementsIInterceptable<IObjectWillChange>,IListenable{values: {[key: string]: ObservableValue<any>|ComputedValue<any>}={}changeListeners=nullinterceptors=nullconstructor(publictarget: any,publicname: 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.")returnregisterListener(this,callback)}intercept(handler): Lambda{returnregisterInterceptor(this,handler)}}
exportfunctionautorun(name: string,view: (r: IReactionPublic)=>any,scope?: any): IReactionDisposerexportfunctionautorun(arg1: any,arg2: any,arg3?: any){letname: string,view: (r: IReactionPublic)=>any,scope: anyif(typeofarg1==="string"){name=arg1view=arg2scope=arg3}else{// 通常调用 autorun(() => console.log('hi', ob.name)) 时 arg1 是函数name=arg1.name||"Autorun@"+getNextId()view=arg1scope=arg2}invariant(typeofview==="function",getMessage("m004"))invariant(isAction(view)===false,getMessage("m005"))if(scope)view=view.bind(scope)constreaction=newReaction(name,function(){this.track(reactionRunner)})functionreactionRunner(){view(reaction)}reaction.schedule()// 开始一个 reactionreturnreaction.getDisposer()}exportclassReactionimplementsIDerivation,IReactionPublic{observing: IObservable[]=[]// nodes we are looking at. Our value depends on these nodesnewObserving: IObservable[]=[]dependenciesState=IDerivationState.NOT_TRACKINGdiffValue=0runId=0unboundDepsCount=0__mapid="#"+getNextId()isDisposed=false_isScheduled=false_isTrackPending=false_isRunning=falseerrorHandler: (error: any,derivation: IDerivation)=>voidconstructor(publicname: string="Reaction@"+getNextId(),privateonInvalidate: ()=>void){}onBecomeStale(){this.schedule()}// 开始一个 reactionschedule(){if(!this._isScheduled){this._isScheduled=true// 放到全局的 pendingReactions 队列中globalState.pendingReactions.push(this)// 然后开始处理所有的 pendingReactions(最终是执行自己的runReaction)runReactions()}}isScheduled(){returnthis._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.trackthis.onInvalidate()}endBatch()}}// 依赖收集,删掉了spyReport相关代码track(fn: ()=>void){startBatch()this._isRunning=trueconstresult=trackDerivedFunction(this,fn,undefined)this._isRunning=falsethis._isTrackPending=falseif(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. */exportfunctiontrackDerivedFunction<T>(derivation: IDerivation,f: ()=>T,context){// 把 derivation.dependenciesState 和 derivation.observing数组内所有 ob.lowestObserverState 改为 IDerivationState.UP_TO_DATE (0)changeDependenciesStateTo0(derivation)// 提前为新 observing 申请空间,之后会trimderivation.newObserving=newArray(derivation.observing.length+100)derivation.unboundDepsCount=0derivation.runId=++globalState.runIdconstprevTracking=globalState.trackingDerivation// 设置 globalState.trackingDerivation 为当前 derivation(autorun创建的reaction)globalState.trackingDerivation=derivation// 初始化工作完毕后执行函数 fn 来追踪哪些 observables 被访问了。letresulttry{// 这一步将会触发 observable 的访问,即我们 ob.name --> $mobx.name.get() (ObservableValue.prototype.get)-->// reportObserved(ObservableValue) !// 很棒,我们上边一步步分析的最终被使用和验证了。result=f.call(context)}catch(e){result=newCaughtException(e)}globalState.trackingDerivation=prevTracking// 到这一步时,虽然 derivation.newObserving[0] 被设置为了 ob.name 对应的 ObservableValue 实例,但真正的绑定处理在下面的 bindDependenciesbindDependencies(derivation)returnresult}/** * diffs newObserving with observing. * update observing to be newObserving with unique observables * notify observers that become observed/unobserved */functionbindDependencies(derivation: IDerivation){constprevObserving=derivation.observingconstobserving=(derivation.observing=derivation.newObserving!)letlowestNewObservingDerivationState=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 itleti0=0,l=derivation.unboundDepsCountfor(leti=0;i<l;i++){constdep=observing[i]if(dep.diffValue===0){dep.diffValue=1if(i0!==i)observing[i0]=depi0++}// Upcast is 'safe' here, because if dep is IObservable, `dependenciesState` will be undefined,// not hitting the conditionif(((depasany)asIDerivation).dependenciesState>lowestNewObservingDerivationState){lowestNewObservingDerivationState=((depasany)asIDerivation).dependenciesState}}observing.length=i0derivation.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 0l=prevObserving.lengthwhile(l--){constdep=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 0while(i0--){constdep=observing[i0]if(dep.diffValue===1){dep.diffValue=0addObserver(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=lowestNewObservingDerivationStatederivation.onBecomeStale()}}
constMAX_REACTION_ITERATIONS=100// 有点奇怪的工具函数:接受一个函数并执行它letreactionScheduler: (fn: ()=>void)=>void=f=>f()exportfunctionrunReactions(){// 如果正在执行 runReactions (globalState.isRunningReactions = true),// 那么直接返回。因为 runReactionsHelper 最终会处理完所有的 pendingReactions,// 只要 push reaction 到 pendingReactions 就可以了。if(globalState.inBatch>0||globalState.isRunningReactions)returnreactionScheduler(runReactionsHelper)}functionrunReactionsHelper(){globalState.isRunningReactions=trueconstallReactions=globalState.pendingReactionsletiterations=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}letremainingReactions=allReactions.splice(0)for(leti=0,l=remainingReactions.length;i<l;i++)remainingReactions[i].runReaction()// 就是执行 reaction 自己的 runReaction 方法}globalState.isRunningReactions=false}
/** * Invoke this method _after_ this method has changed to signal mobx that all its observers should invalidate. */publicreportChanged(){startBatch()propagateChanged(this)endBatch()}// Called by Atom when its value changesexportfunctionpropagateChanged(observable: IObservable){// invariantLOS(observable, "changed start");if(observable.lowestObserverState===IDerivationState.STALE)returnobservable.lowestObserverState=IDerivationState.STALEconstobservers=observable.observersleti=observers.lengthwhile(i--){constd=observers[i]if(d.dependenciesState===IDerivationState.UP_TO_DATE)d.onBecomeStale()d.dependenciesState=IDerivationState.STALE}// invariantLOS(observable, "changed end");}
在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
ObservableObjectAdministration
比较简单,从两个关键属性values
/changeListeners
的名字就可以了解大概了。但是ObservableObjectAdministration
本身并没有涉及到复杂的逻辑,即values
/changeListeners
怎么创建,怎么使用的都没有涉及,所以我们先回到起点,一步步来:上面罗列了创建一个 observable 对象(
ob
)的步骤,可以看到,asObservableObject
这一步添加了$mobx
属性,但结合上面ObservableObjectAdministration
类的定义,我们知道这一步其实并没有发生任何神奇的事,所以真正关键的在extendObservable
里:跟随上面一步步追下来,我们最终可知道,当我们访问
ob.name
时,其实触发的是ObservableValue
的get
方法。observable 对象(ob
)上的属性(和data
同名的那些)基本只是一个包装,处理逻辑都在ObservableValue
里,包括依赖收集和更新通知。ObservableValue
从代码来看,我们已经看到关键的 get/set 函数了,看到
reportObserved/notifyListeners
等预期的东西了。依赖收集怎么完成的?
通过上面的代码,我们已经知道当我们访问一个属性时的调用链路了,那么,依赖是怎么收集的呢(最终一步
reportObserved
)?autorun
&Reaction
&trackDerivedFunction
结论:
autorun(fn)
执行后(fn
访问 observable),创建了一个 reaction,并建立以下依赖关系:reaction.observing (数组)包含
fn
访问的所有 ObservableValue 实例;每个 ObservableValue 实例的属性 observers (数组)包含 reaction。
更新通知
当我们更新
ob.name
时,这个变化怎么通知到依赖于ob.name
的我们的 reaction ?相关代码其实之前已经贴出:
不过原本以为通知是
notifyListeners
触发的,实际上,其实是reportChanged
来触发。这可能出乎意料,但考虑到reportObservabled
用于收集依赖,reportChanged
显得对称而合理。如上,最终其实是执行了
reaction.onBecomeStale() --> reaction.schedule()
,即最终回到了同一条路径。The text was updated successfully, but these errors were encountered: