var student = mobx.observable({
language: 100,
mathematics: 90,
name:'张三'
});
var total = mobx.computed(() => {
return student.language + student.mathematics;
});
mobx.autorun(() => {
console.log(student.name + '的总分:' + total);
});
student.mathematics = 100;
1.computed函数
export const computed: IComputed = function computed(arg1, arg2, arg3) {
if (typeof arg2 === "string") {
// @computed
return computedDecorator.apply(null, arguments)
}
if (arg1 !== null && typeof arg1 === "object" && arguments.length === 1) {
// @computed({ options })
return computedDecorator.apply(null, arguments)
}
// arg2为object或function,
// 为object则为IComputedValueOptions
// 为function则为setter
const opts: IComputedValueOptions<any> = typeof arg2 === "object" ? arg2 : {}
opts.get = arg1
opts.set = typeof arg2 === "function" ? arg2 : opts.set
opts.name = opts.name || arg1.name || "" /* for generated name */
return new ComputedValue(opts)
} as any
computed.struct = computedStructDecorator
先跳过前边两个if(装饰器部分),直接看computed(...),通过IComputed来看,computed(...)的参数为两种情况。
- (func: () => T, setter: (v: T) => void)
- (func: () => T, options?: IComputedValueOptions)
interface IComputed {
<T>(options: IComputedValueOptions<T>): any // decorator
<T>(func: () => T, setter: (v: T) => void): IComputedValue<T> // normal usage
<T>(func: () => T, options?: IComputedValueOptions<T>): IComputedValue<T> // normal usage
(target: Object, key: string | symbol, baseDescriptor?: PropertyDescriptor): void // decorator
struct: (target: Object, key: string | symbol, baseDescriptor?: PropertyDescriptor) => void // decorator
}
computde方法返回了一个 ComputedValue类型的实例。
class ComputedValue<T> implements IObservable, IComputedValue<T>, IDerivation
从上边的代码可以发现,ComputedValue实现了IObservable和IDerivation。这两个类型应该并不陌生,我们之前梳理autorun和observable的时候也看到过。
Reaction也实现IDerivation, IAtom继承自IObservable。这应该能看出来ComputedValue即实现了Reaction (响应)的功能也实现了Observable(被观察者)的功能。
实际上ComputedValue是一个中间层,当Reaction调用它的时候,ComputedValue就像一个Observable一样调用它的get方法处理一些事务,例如调用reportObserved。
当ComputedValue调用一个Observable时候,又触发它下一级的个Observable调用get方法。
当然ComputedValue也走自己独特之处。
1.1 计算值的get方法
public get(): T {
// globalState.inBatch === 0 轻量级计算
if (globalState.inBatch === 0 && this.observers.size === 0 && !this.keepAlive) {
if (shouldCompute(this)) {
this.warnAboutUntrackedRead()
startBatch() // See perf test 'computed memoization'
this.value = this.computeValue(false)
endBatch()
}
} else {
// globalState.inBatch > 0 重量级计算
reportObserved(this)
if (shouldCompute(this)) if (this.trackAndCompute())
propagateChangeConfirmed(this)
}
const result = this.value!
return result
}
get方法根据globalState.inBatch === 0 划分了两种计算方式
- 1)globalState.inBatch === 0 调用computeValue
- 2)globalState.inBatch > 0 调用trackAndCompute
我们先将他们分别命名为1)轻量级计算、2)重量级计算;接下来我会说明为什么叫做轻量级和重量级。
1.1.1 computeValue方法通过执行回调函数进行计算
computeValue(track: boolean) {
let res: T | CaughtException
if (track) {
res = trackDerivedFunction(this, this.derivation, this.scope)
} else {
res = this.derivation.call(this.scope)
}
return res
}
computeValue根据参数track又划分了两种情况
- 1)track为true,调用trackDerivedFunction(trackDerivedFunction之前也说到过,除了执行回调还会更新依赖关系、处理状态等)计算
- 2)track为false,调用this.derivation(computed的回调)计算
轻量级计算中computeValue(false)参数为false所以,直接调用this.derivation方法计算,所以称这种计算方式为轻量级计算。
1.1.2 trackAndCompute
重量级计算种调用了computeValue(true),此时参数为true。采用trackDerivedFunction的计算方式,先执行回调再更新依赖关系。
private trackAndCompute(): boolean {
...
const newValue = this.computeValue(true)
const changed =
wasSuspended ||
isCaughtException(oldValue) ||
isCaughtException(newValue) ||
!this.equals(oldValue, newValue)
if (changed) {
this.value = newValue
}
return changed
}
2 mobx的状态调整策略。
说完了计算方式,我们再来看一下这几个方法 propagateChanged,propagateChangeConfirmed,propagateMaybeChanged,shouldCompute
export function propagateChanged(observable: IObservable) {
if (observable.lowestObserverState === IDerivationState.STALE) return
observable.lowestObserverState = IDerivationState.STALE
observable.observers.forEach(d => {
if (d.dependenciesState === IDerivationState.UP_TO_DATE) {
d.onBecomeStale()
}
d.dependenciesState = IDerivationState.STALE
})
}
---------------------------------------------------------
export function propagateChangeConfirmed(observable: IObservable) {
if (observable.lowestObserverState === IDerivationState.STALE) return
observable.lowestObserverState = IDerivationState.STALE
observable.observers.forEach(d => {
if (d.dependenciesState === IDerivationState.POSSIBLY_STALE)
d.dependenciesState = IDerivationState.STALE
else if (
d.dependenciesState === IDerivationState.UP_TO_DATE
)
observable.lowestObserverState = IDerivationState.UP_TO_DATE
})
}
---------------------------------------------------------
export function propagateMaybeChanged(observable: IObservable) {
if (observable.lowestObserverState !== IDerivationState.UP_TO_DATE) return
observable.lowestObserverState = IDerivationState.POSSIBLY_STALE
observable.observers.forEach(d => {
if (d.dependenciesState === IDerivationState.UP_TO_DATE) {
d.dependenciesState = IDerivationState.POSSIBLY_STALE
d.onBecomeStale()
}
})
}
---------------------------------------------------------
export function shouldCompute(derivation: IDerivation): boolean {
switch (derivation.dependenciesState) {
case IDerivationState.UP_TO_DATE:
return false
case IDerivationState.NOT_TRACKING:
case IDerivationState.STALE:
return true
case IDerivationState.POSSIBLY_STALE: {
const prev = globalState.trackingDerivation
globalState.trackingDerivation = null
const obs = derivation.observing,
l = obs.length
for (let i = 0; i < l; i++) {
const obj = obs[i]
if (isComputedValue(obj)) {
obj.get()
if ((derivation.dependenciesState as any) === IDerivationState.STALE) {
globalState.trackingDerivation = pre
return true
}
}
}
changeDependenciesStateTo0(derivation)
globalState.trackingDerivation = pre
return false
}
}
}
先粗略的观察一下上边的这几个函数,我们可以看到两个常用的状态属性dependenciesState(接下来简称D属性),lowestObserverState(接下来简称L属性); mobx就是利用这两个属性控制是否执行计算的。
我们重点梳理一下dependenciesState,lowestObserverState;
- 1)dependenciesState来源于Reaction类的,是响应者的状态属性;
- 2)lowestObserverState来源于IObservable类,是被观察者的状态属性;
- 3)ComputedValue同时拥有dependenciesState、lowestObserverState两个属性,因为它有时候充当被观察者,有时候充当响应。
这两种状态值取自同一个枚举值IDerivationState
export enum IDerivationState {
NOT_TRACKING = -1, // 未跟踪的
UP_TO_DATE = 0, // 最新的
POSSIBLY_STALE = 1, // 可能是不新鲜的
STALE = 2 // 不新鲜的
}
他们的代办的作用和字面上意思一致
- 1)UP_TO_DATE为最新状态,处在这个状态时候不需要进行更新操作
- 2)NOT_TRACKING为未跟踪的状态值,处在这个状态需要更新为另外三种状态
- 3)STALE不新鲜,需要更新
- 4)POSSIBLY_STALE可能不新鲜,先要查看他的子级(obsering集合)确认是否需要更新。
再回过头来分析上边提到的四个函数propagateChanged,propagateChangeConfirmed,propagateMaybeChanged,shouldCompute。
2.1 修改被观察值,修改L和D属性
第一个函数propagateChanged应该不陌生,在梳理被观察者observable时,执行更新(set)操作会调用此函数。在此函数种会对L和D属性进行修改。我们以头部的案例为代表进行分析。
var student = mobx.observable({
language: 100,
mathematics: 90,
name:'张三'
});
var total = mobx.computed(() => {
return student.language + student.mathematics;
});
mobx.autorun(() => {
console.log(student.name + '的总分:' + total);
});
student.mathematics = 100;
当修改student.mathematics值为100的时候,就会调用student.mathematics.set(), 再调用student.mathematics.reportChanged(),最后调用了propagateChanged函数。
public reportChanged() {
startBatch()
propagateChanged(this)
endBatch()
}
export function propagateChanged(observable: IObservable) {
if (observable.lowestObserverState === IDerivationState.STALE) return
observable.lowestObserverState = IDerivationState.STALE
observable.observers.forEach(d => {
if (d.dependenciesState === IDerivationState.UP_TO_DATE) {
d.onBecomeStale()
}
d.dependenciesState = IDerivationState.STALE
})
}
- 1)此时会将被观察者student.mathematics的L属性置为STALE(2)
- 2)student.mathematics.observers集合中,响应对象的D属性也为STALE(student.mathematics.observers存储的是依赖它的计算值total,也就是将total的D属性置为2)
- 3)此函数中,在修改total.D之前会先调用total.onBecomeStale方法。
2.2 通知被观察值的上一级,观察值发生了变动
propagateChanged函数,通过调用total.onBecomeStale方法告诉计算值,你依赖的值有变动。计算值调用propagateMaybeChanged函数,来更新自身及上一级的状态
onBecomeStale() {
propagateMaybeChanged(this)
}
export function propagateMaybeChanged(observable: IObservable) {
if (observable.lowestObserverState !== IDerivationState.UP_TO_DATE) return
observable.lowestObserverState = IDerivationState.POSSIBLY_STALE
observable.observers.forEach(d => {
if (d.dependenciesState === IDerivationState.UP_TO_DATE) {
d.dependenciesState = IDerivationState.POSSIBLY_STALE
d.onBecomeStale()
}
})
}
propagateMaybeChanged修改了totalL属性和上一级D属性。
- 1)将total的L属性只为UP_TO_DATE(1),意思是他的下级可能被修改了。
- 2)修改依赖它响应的D属性(reaction.D)也修改为UP_TO_DATE
-
- 通过调用reaction.onBecomeStale方法通知reaction子级有变动。
reaction.onBecomeStale在讲autorun的时候也提到过。我们列出一些主要步骤
- 1)调用reaction.schedule()
- 2)将reaction存入globalState.pendingReactions队列中;
- 2)调用runReactions函数。
schedule() {
if (!this._isScheduled) {
this._isScheduled = true
globalState.pendingReactions.push(this)
runReactions()
}
}
----------------------------------------------
export function runReactions() {
if (globalState.inBatch > 0 || globalState.isRunningReactions) return
reactionScheduler(runReactionsHelper)
}
但是在执行student.mathematics.set()时,已经开启一层任务即执行startBatch函数(globalState.inBatch++);
public reportChanged() {
startBatch()
propagateChanged(this)
endBatch()
}
导致此时调用runReactions函数时globalState.inBatch > 0,会直接retrun。也就是此时正在执行任务种,不会进行更新操作,但是已经将等待执行的操作存到了globalState.pendingReactions队列种。
我们先来整理一下此刻相关变量的状态值
被观察者 student.mathematics.L 为 2
计算值 total.D 为 2,total.L 为 1
响应 reaction.D 为 1
执行到这里从修改被观察值后,通过propagateChanged函数层层上报,更改各级状态的 工作基本就完成了。接着执行endBatch函数结束这一层任务。
然而我们发现student.mathematics值的修改好像除了修改状态什么都没有做,并没有使total重新计算,也没有使autorun再次执行。
不着急,我们再来看一下一直被忽视的endBatch函数。
2.2 endBatch函数,重新执行Reaction的部署操作
export function endBatch() {
if (--globalState.inBatch === 0) {
runReactions()
....
}
}
又出现了runReactions函数,是不是豁然开朗了。也就是说在endBatch中会使globalState.inBatch-1。并且如果globalState.inBatch===0,也就是没有正在执行的事务了,会再次调用runReactions函数。
export function runReactions() {
if (globalState.inBatch > 0 || globalState.isRunningReactions) return
reactionScheduler(runReactionsHelper)
}
function runReactionsHelper() {
globalState.isRunningReactions = true
const allReactions = globalState.pendingReactions
while (allReactions.length > 0) {
let remainingReactions = allReactions.splice(0)
for (let i = 0, l = remainingReactions.length; i < l; i++)
remainingReactions[i].runReaction()
}
globalState.isRunningReactions = false
}
//------------------------------Reaction---------------------------
runReaction() {
if (!this.isDisposed) {
startBatch()
this._isScheduled = false
if (shouldCompute(this)) {
this.onInvalidate() // 通过构造函数传入,最终调用了track方法
}
endBatch()
}
}
track(fn: () => void) {
startBatch()
const result = trackDerivedFunction(this, fn, undefined)
endBatch()
}
上面这一串操作应该也不陌生,这串操作种会有一些岔路口,为了理清脉络我们用A、B、C...来命名
A)runReactions函数中,先取出globalState.pendingReactions队列中等待执行的Reaction,遍历依次调用他们的runReaction方法。
B)判断shouldCompute(this)返回结果
C)调用track函数,调用trackDerivedFunction函数
2.3 shouldCompute函数判断是否执行计算。
先来分析B的判断函数shouldCompute
export function shouldCompute(derivation: IDerivation): boolean {
switch (derivation.dependenciesState) {
case IDerivationState.UP_TO_DATE:
return false
case IDerivationState.NOT_TRACKING:
case IDerivationState.STALE:
return true
case IDerivationState.POSSIBLY_STALE: {
const prev = globalState.trackingDerivation
globalState.trackingDerivation = null
const obs = derivation.observing,
l = obs.length
for (let i = 0; i < l; i++) {
const obj = obs[i]
if (isComputedValue(obj)) {
obj.get()
if (derivation.dependenciesState === IDerivationState.STALE) {
globalState.trackingDerivation = pre
return true
}
}
}
changeDependenciesStateTo0(derivation)
globalState.trackingDerivation = pre
return false
}
}
}
shouldCompute根据传入值的D属性,分了3中策略进行处理
- 1)值为UP_TO_DATE(0)返回false即不进行计算
- 2)值为NOT_TRACKING(-1)或STALE(2)返回true,需要进行计算。
- 3)值为POSSIBLY_STALE(1)分为两种,根据reaction.observing集合中是否有计算值划分两种情况
- 3.1)无计算值返回false
- 3.2)有计算值调用计算值的get方法后,再判断reaction的D属性为STALE(2)返回true否则false
此时reaction的D属性也正好是POSSIBLY_STALE(1),所以会调用total.get方法。
计算值的get方法,开头我们讲了,再重新看一下
public get(): T {
// globalState.inBatch === 0 轻量级计算
if (globalState.inBatch === 0 && this.observers.size === 0 && !this.keepAlive) {
if (shouldCompute(this)) {
this.warnAboutUntrackedRead()
startBatch() // See perf test 'computed memoization'
this.value = this.computeValue(false)
endBatch()
}
} else {
// globalState.inBatch > 0 重量级计算
reportObserved(this)
if (shouldCompute(this)) if (this.trackAndCompute())
propagateChangeConfirmed(this)
}
const result = this.value!
return result
}
get中分为轻量和重量两种计算方式,此时会执行重量级计算
- B-1)执行reportObserved()函数,此时因为在shouldCompute中将globalState.trackingDerivation置为null,所以不会有操作。
- B-2)有来了一个shouldCompute函数,这次会根据total的D属性判断,此时total.D为2故返回true。
- B-3)trackAndCompute函数,trackAndCompute会调用computeValue,computeValue调用trackDerivedFunction函数,所以直接来看trackDerivedFunction函数。
export function trackDerivedFunction<T>(derivation: IDerivation, f: () => T, context: any) {
changeDependenciesStateTo0(derivation)
...
let result = f.call(context)
...
bindDependencies(derivation)
return result
}
- B-3-1)changeDependenciesStateTo0函数将total.D属性重新设置为0,再将存在observing属性中的依赖值L属性也变成0,也就是student.mathematics.L重新置为0。
此时变量的状态值
被观察者 student.mathematics.L 为 0
计算值 total.D 为 0,total.L 为 1
响应 reaction.D 为 1
- B-3-2) f.call(context)重新执行了计算。
- B-4)执行propagateChangeConfirmed函数,此时total.L为1所以会执行下边的操作修改状态
export function propagateChangeConfirmed(observable: IObservable) {
if (observable.lowestObserverState === IDerivationState.STALE) return
observable.lowestObserverState = IDerivationState.STALE
observable.observers.forEach(d => {
if (d.dependenciesState === IDerivationState.POSSIBLY_STALE)
d.dependenciesState = IDerivationState.STALE
else if (
d.dependenciesState === IDerivationState.UP_TO_DATE
)
observable.lowestObserverState = IDerivationState.UP_TO_DATE
})
}
- 1)修改total.L为2。
- 2)遍历total.observers集合,即调用total的响应Reaction,目前只有reaction,即reaction.D 从 1修改为2。
此时变量的状态值
被观察者 student.mathematics.L 为 0
计算值 total.D 为 0,total.L 为 2
响应 reaction.D 为 2
回到B的shouldCompute判断种,此时reaction.D 为 2所以返回true。
2.4 trackDerivedFunction函数将状态全部归零。
C操作又一次调用了trackDerivedFunction执行计算。
export function trackDerivedFunction<T>(derivation: IDerivation, f: () => T, context: any) {
changeDependenciesStateTo0(derivation)
...
let result = f.call(context)
...
bindDependencies(derivation)
return result
}
- C-1)changeDependenciesStateTo0将reaction.D和,他的下一级total的L属性设置为0;
- C-2)f.call(context)重新执行计算。
- C-3) bindDependencies更新依赖关系。 此时变量的状态值
被观察者 student.mathematics.L 为 0
计算值 total.D 为 0,total.L 为 0
响应 reaction.D 为 0
此时状态已经全部归0,total和reaction也都重新执行了计算。一次修改到更新的操作流程已经捋清楚了。
总结
computed的文档中有这样一段描述
通过上边说的“状态调整策略”再来理解这句话,为什么说“计算中使用的数据没有更改,计算属性将不会重新运行”就很明白了。
- 1)只有数据更改,才会修改提升自己的L属性和依赖值D属性,并层层上报修改状态层层修改状态。
- 2)向下执行时候,会调用shouldCompute函数通过判断D属性决定否需要更新,更新过程中会逐步将状态归零。