mobx 学习笔记
在 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()
,即最终回到了同一条路径。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
上一篇: 租用服务器常说到的路、核、线程是什么?
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论