Mobx 源码分析 - computed
computed 只会在值用到的时候才会执行函数
用法
下文会以此作为依据进行讲解
class Foo{
@computed get age() { return expr; } // 推荐用法
}
2
3
源码剖析
属性劫持
通过调用 createPropDecorator 方法给原型对象创建一个不可枚举的 __mobxDecorators 属性对象,age 会作为属性对象的 key 存在,此方法会返回一个描述符。
// 添加 __mobxDecorators 属性
const inheritedDecorators = target.__mobxDecorators
addHiddenProp(target, "__mobxDecorators", { ...inheritedDecorators })
// 描述符
{
configurable: true,
enumerable: enumerable,
get() {
initializeInstance(this)
return this[prop]
},
set(value) {
initializeInstance(this)
this[prop] = value
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
我们发现这里劫持了属性的 get 和 set 操作,每当执行 get 和 set 操作之前,都会先执行 initializeInstance。
initializeInstance
判断实例上 __mobxDidRunLazyInitializers 是否为 true,如果是则代表对象已经处于可监听状态。否则取原型上的 __mobxDecorators 对象,并遍历此对象的每一个 propertyCreator 方法,也就是之前 createPropDecorator 方法的第二个参数。propertyCreator 方法内部把描述符上 get 和 set 与 decoratorArgs[0] 当做 defineComputedProperty 第三个参数传递进去。
const { get, set } = descriptor
const options = decoratorArgs[0] || {}
defineComputedProperty(instance, propertyName, { get, set, ...options })
2
3
defineComputedProperty
const adm = asObservableObject(target)
options.name = `${adm.name}.${propName}`
options.context = target
adm.values[propName] = new ComputedValue(options)
Object.defineProperty(target, propName, generateComputedPropConfig(propName))
2
3
4
5
asObservableObject 主要作用为在实例上新增不可枚举的 $mobx 属性,age 会作为属性的 key 存在,属性值为 ObservableObjectAdministration 实例。
修改 adm.values[propName] 为 ComputedValue 的实例,实例上新增不可枚举的 age 属性,并劫持 get 和 set,每当访问或设置 age 属性时,实际上是访问 $mobx 原型上的 read 和 write。
function generateComputedPropConfig(propName){
return {
get() {
return getAdministrationForComputedPropOwner(this).read(this, propName)
},
set(v) {
getAdministrationForComputedPropOwner(this).write(this, propName, v)
}
}
}
function getAdministrationForComputedPropOwner(owner) {
const adm = owner.$mobx
if (!adm) {
initializeInstance(owner)
return owner.$mobx
}
return adm
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
ObservableObjectAdministration
前文说过,访问 age 实际上是访问 $mobx 上的 read,而 read 又返回 values.age 值,也就是相当于访问 ComputedValue 实例的 get 方法。总结下,访问 age 相当于访问 ComputedValue 实例的 get 方法。
ComputedValue
如果当前不处于事务处理,又没有观察者且 keepAlive 为 false 状态,则开启事务处理,并执行 age 函数,并把返回值保存在 value 中,并结束事务处理。
if (globalState.inBatch === 0 && this.observers.length === 0 && !this.keepAlive) {
// NOT_TRACKING
if (shouldCompute(this)) {
this.warnAboutUntrackedRead();
startBatch(); // See perf test 'computed memoization'
this.value = this.computeValue(false);
endBatch();
}
}
2
3
4
5
6
7
8
9
如果当前处于事务处理或有观察者或 keepAlive 为 true,则调用 reportObserved,在正在跟踪的 derivation 中加入当前实例,并设置实例已处于监听状态;如果当前实例没有观察者且当前处于事务中,则把当前实例放入到队列中,等待移除监听状态。执行 age 函数,并刷新监听对象依赖关系,把 age 函数执行结果返回出去,并用此值与旧值进行比对,如果不一致,则使用新值,并改变内部状态。
function computeValue(track){
...
res = trackDerivedFunction(this, this.derivation, this.scope)
...
}
2
3
4
5