在 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` doesnt 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 shouldnt 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: its not in new observables, unobserve it
// 1: it keeps being observed, dont 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. dont need to do anything.
// 1: it wasnt observed, lets 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 doesnt 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()
,即最终回到了同一条路径。
请登录后查看评论内容