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