调试的准备工作
创建一个ts的react-app项目
将mobx源码移入到新创建的react-app项目 注意是ts的项目
因为我们的ts的react项目的tsconfig文件配置有isolatedModules这一项我们就需要将mobx源码所有导出的类型 重新export type {}方式导出一遍如图所示:
注
isolatedModules这一编译选项最好不要设置false或者删除虽然这样就不必重新导出类型了
接着tsconfig仍需要添加这几项:
//在tsconfig.json内compilerOptions中
compilerOptions: {
"noImplicitAny": false,
"noImplicitThis":false,
"downlevelIteration": true,
}
添加以上三项分别是noImplicitAny隐式any类型设置false为不报错允许通过,noImplicitThis,设置false不严格检查this的隐式类型,downlevelIteration,降低迭代器版本,迭代器生成器函数返回或一些js的数据结构详情请看js相关资料,由于新语言特性的迭代器和旧的不同,为了让旧的可以运行就必须降低迭代器版本。
更改源码,将Lambda类型更改为:
export interface Lambda {
(value?:any ):void
name?: string
}
将ownkeys返回类型由PropertyKey变为string|symbol
(在dynamicobject.ts、observableobject.ts和utils.ts中)
IMapDidChange<unknown, unknown>改成IMapDidChange<unknown|any, unknown|any>(在spy.ts中)
做完以上几项基本也就可以开始调试了
mobx程序逻辑概述
mobx响应原理的关键
响应的系统的简单实现是依赖于Object.defineProperty和Proxy
Object.defineProperty用于属性类型是基本类型(number,string,boolean等)对其进行设置,一般设置该属性的get,set,以用来在读取和赋值时应执行哪些操作
Proxy同样是对对象属性的get,set进行操作,但不同是Proxy用于对于复合对象的操作响应的实现如Array,map,set等类型。
响应式的优点
看下图
当没有使用响应时,修改obj.a的值与其在改变时应该执行的逻辑需要放在同一个域内,
这表示当只要修改obj.a时就需要在其域内写上改变时执行的对应逻辑,这样使得每次改变都需要在下面写上改变的逻辑,非常麻烦且容易出错。
当使用响应时,我们只需在一处写改变时执行的逻辑就可以了,当obj.a被改变时自动执行设置好的对应方法,我们就不需要时刻提醒自己在改变obj.a时应该执行哪些方法。
mobx响应系统概述
首先看使用示例
this为传入的对象,方法的第二参数是一个配置对象,左边属性是传入的this里所有的属性,右边是对改属性的操作,也被成为对该属性的修饰。
这里先说下observable它是响应式的关键之一,经由它开始对修饰属性设置get,set
这里简要说下mobx的四个个基本功能模块:
- 修饰模块 上图中在属性后面的各个值,在mobx中称为修饰,这里称它为修饰模块常用到的便是代码示例所展示的
- 管理模块
ObservableObjectAdministrationmakeObservable内部直接创建一个ObservableObjectAdministration对象,然后用它调用修饰功能 - 真实功能模块 它提供被设置get,set时所绑定执行的具体逻辑,也是mobx响应功能的核心,读取时执行什么,写入时执行什么都在该模块中。
- 反应模块
reactionmobx提供给用户用来自定义响应时应该执行什么的功能模块,它被真实功能模块所连接,所以在响应时得以执行
reaction 使用示例
reaction方法中有两个参数,分别是读取被observable修饰的属性,该属性被改变时执行的逻辑
所以当testMobx.value被改变时 下面就会打印出被改变的值以及'获取响应'。
重要一点是当第一参数中没有读取testMobx.value或且没有返回testMobx.value,第二参数将不会执行
这也是mobx的响应系统的一种特点,读取时注册,写入时响应
mobx响应实现的基本流程
我们从最简单流程入手,也就是设置响应的属性值为基本类型时(string,number,boolean...)的过程
class Doubler {
value=0
array=[]
constructor(value) {
makeObservable(this, {
value: observable,
array:observable,
double: computed,
increment: action,
})
}
...
}
也就是上述示例代码中对value属性设置响应的过程,value是number类型
先看一下 ObservableObjectAdministration内几个关键方法
make_ 它由makeObservable调用
它会调用修饰的make_方法 这里我们先只看响应的实现所以
annotation当做observable
来到observable这里,来看它的定义
export var observable: IObservableFactory = assign(createObservable, observableFactories)
可以得知它是由createObservable, observableFactories两个合并而来
我们从最简单流程入手,这里关键方法为createObservable
继续看
Object.assign(createObservable, observableDecoratorAnnotation)
看源码我们得知createObservable与observableDecoratorAnnotation接下来的关键是observableDecoratorAnnotation
直接看
createObservableAnnotation
function createObservableAnnotation(name: string, options?: object): Annotation {
return {
annotationType_: name,
options_: options,
make_,
extend_
}
}
注意make_ 由ObservableObjectAdministration调用的就是此方法
function make_(
adm: ObservableObjectAdministration,
key: PropertyKey,
descriptor: PropertyDescriptor
): MakeResult {
return this.extend_(adm, key, descriptor, false) === null ? MakeResult.Cancel : MakeResult.Break
}
make_又调用了extend_
function extend_(
adm: ObservableObjectAdministration,
key: PropertyKey,
descriptor: PropertyDescriptor,
proxyTrap: boolean
): boolean | null {
assertObservableDescriptor(adm, this, key, descriptor)
//console.log('设置可响应')
return adm.defineObservableProperty_(
key,
descriptor.value,
this.options_?.enhancer ?? deepEnhancer,
proxyTrap
)
}
直接看重点adm.defineObservableProperty_这里的adm就是当执行 const outcome = annotation.make_(this, key, descriptor, source)时传入的this便是adm
adm.defineObservableProperty_方法的核心便是调用Object.defineProperty
内部主要过程如下
先是获取被修饰属性的描述符
const cachedDescriptor = getCachedObservablePropDescriptor(key)
const descriptor = {
configurable: globalState.safeDescriptors ? this.isPlainObject_ : true,
enumerable: true,
get: cachedDescriptor.get,
set: cachedDescriptor.set
}
defineProperty(this.target_, key, descriptor)
注意看这里defineProperty实际就是Object.defineProperty,
而设置属性的get,set方法从cachedDescriptor取得
继续向下
//这里创建observableValue
const observable = new ObservableValue(
value,
enhancer,
__DEV__ ? `${this.name_}.${key.toString()}` : "ObservableObject.key",
false
)
this.values_.set(key, observable)
this.value_是一个Map类型实例,由ObservableObjectAdministration被实例化传入,由上述代码可知他将被修饰的key以及observable存入此map中,这个this.value_有什么用呢?
看上面代码中的getCachedObservablePropDescriptor
function getCachedObservablePropDescriptor(key) {
//descriptorCache Object.create(null)创建出来没有原型的纯{}对象
//this是用户自定义的对象,this[$mobx]是ObservableObjectAdministration对象内部value_是Map存储this的一些被annotation修饰过的key,以及相对应的对象如ObserValue
return (
descriptorCache[key] ||
(descriptorCache[key] = {
get() {
return this[$mobx].getObservablePropValue_(key)//响应系统关键点
},
set(value) {
return this[$mobx].setObservablePropValue_(key, value)
}
})
)
}
getObservablePropValue_(key: PropertyKey): any {
return this.values_.get(key)!.get()
}
setObservablePropValue_(key: PropertyKey, newValue): boolean | null {
const observable = this.values_.get(key)
//省略一些代码来看主要部分
(observable as ObservableValue<any>).setNewValue_(newValue)
...
由此我们可以得知对于示例代码 属性value的绑定,对其设置get,set的方法都由observable提供,而this.values_存储了它
以上流程可以简要概况为,ObservableObjectAdministration调用自身make_方法,之后用被调用了自身defineObservableProperty_实现了对被修饰key的get,set的设置
而设置get,set的方法内部都做了哪些事?接下来深入了解observable
实体功能模块 ObservableValue
前面所说实体功能模块提供的系统的功能核心,也知道变量observable提供了get,set时需要执行的方法。接下来来看这两个方法get,setNewValue_。
首先是get:
public get(): T {
this.reportObserved()
return this.dehanceValue(this.value_)
}
this.dehanceValue(this.value_)实际上返回this.value_由ObservableObjectAdministration调用自身方法defineObservableProperty_创建observable示例得来
const observable = new ObservableValue(
value,
enhancer,
__DEV__ ? `${this.name_}.${key.toString()}` : "ObservableObject.key",
false
)
第一参value赋值给内部的value_属性,而这个value是通过获取修饰属性的描述符得来的代码在ObservableObjectAdministration.make_中,const descriptor = getDescriptor(source, key)
实际上掉用的是Object.getOwnPropertyDescriptor。
这个value本质上就是被修饰属性的值,mobx使用示例代码中被修饰属性是value,值为2,将属性的值存入到了observable。也就是说当此被修饰的属性读取时,获取到的是observable.value_
接下来我们看关键的this.reportObserved(),如何与reaction建立连接,靠的就是它,上面说过mobx是读取时注册,写入时响应,在reaction示例代码中就有体现,当我们在第一参读取时,实际就把第二参的方法绑定在被修饰属性set中。
function reportObserved(observable: IObservable): boolean {
checkIfStateReadsAreAllowed(observable)
const derivation = globalState.trackingDerivation
if (derivation !== null) {
/**
* Simple optimization, give each derivation run an unique id (runId)
* Check if last time this observable was accessed the same runId is used
* if this is the case, the relation is already known
*/
//经过测试如果想要响应的变量如果没在reaction的第一个参数返回出来即使是读了一遍
//也不响应判断逻辑为之下 globalState.trackingContext是reaction
if (derivation.runId_ !== observable.lastAccessedBy_) {
observable.lastAccessedBy_ = derivation.runId_
// Tried storing newObserving, or observing, or both as Set, but performance didn't come close...
derivation.newObserving_![derivation.unboundDepsCount_++] = observable
if (!observable.isBeingObserved_ && globalState.trackingContext) {
observable.isBeingObserved_ = true
observable.onBO()
}
}
return true
} else if (observable.observers_.size === 0 && globalState.inBatch > 0) {
queueForUnobservation(observable)
}
return false
}
这里注意看const derivation = globalState.trackingDerivation 然后再看derivation.newObserving_![derivation.unboundDepsCount_++] = observable,我们看到正在执行读取操作的属性,其observable被放入了derivation.newObserving_数组中。
reaction的第一参数中读取被observable修饰的属性才能对这个属性进行监听响应原因在于这里,当reaction执行时它会把自身赋值给globalState.trackingDerivation,这时执行第一参的方法,就完成了注册。(reaction获取到observable)
reaction内部实例化一个Reaction的对象 并调用track方法,接着方法内部执行了globalState.trackingContext = this
反应模块reaction
接下来到属性赋值时响应功能的解析了,由上述当被修饰的属性在被赋值写入时,执行的是observable.setNewValue_
现在我们知道对象Reaction的实例有一个newObserving_属性类型为数组储存了在reaction内被读取的Observable
我们不妨设想一下写入时响应应该是如何实现
如果紧靠调用reaction存储的Observable实现响应行不行?也就是说当observable.setNewValue_执行时仅靠遍历newObserving_中的observable?
然后判断这时发生的响应observable是谁,然后再执行写入reaction的第二参,执行用户自定义的响应逻辑,貌似可行。
但存在一个致命问题,在上述情况下,当reaction只有一个时,是可行的,但如果有多个,那又如何去寻找被写入的observable在哪个reaction,设置一个全局reaction数组?,那维护词此数组所带来的代价有点大,尤其为了一个observable响应,去遍历所有存在的reaction,未免有些过于浪费
mobx在读取时注册依赖,写入时响应依赖
将关注点集中在observable上,当setNewValue时,为何不去在observable上找哪个reaction与其绑定,还有一点绑定这个observable的reaction会只有一个么?想想也当然不会。
所以observable上必然有一个属性用来存储reaction类似于Reaction实例对象上newObserving_存储observable一样。
不过说回来为什么newObserving_是一个数组,而不是一个普通对象类型属性只存储一个observable?
因为在reaction第一参中的方法内,不一定只读取一个被observable修饰的属性
在observable.set方法中,我们只看到了observable被reaction收入囊中,而reaction并未被observable收集,那收集的逻辑在哪里呢?
当然不会在setNewValue_中在那里绑定也太晚了,而且去寻找绑定的reaction的逻辑就会和我上述所说的一样
既然在observable.set在执行时将自身给了reaction,那同样reaction在执行自身逻辑时,应该也一样把自身给observable实现双向绑定,响应依赖的核心其实仍是observable遍历自己存储的reaction,而reaction的作用就是将自己绑定在observable上
接下来我们看reaction内部的逻辑
const r = new Reaction(
name,
() => {
if (firstTime || runSync) {
reactionRunner()
} else if (!isScheduled) {
isScheduled = true
scheduler!(reactionRunner)
}
},
opts.onError,
opts.requiresObservable
)
创建了Reaction对象,第二参是个方法,必然由React对象自行调用,关键在于reactionRunner方法
它同样定义在reaction方法内
function reactionRunner() {//在set时会再度调用此方法 runReactionsHelper会调用Reaction中runReaction_方法进而调用这里
isScheduled = false
if (r.isDisposed_) return
let changed: boolean = false
r.track(() => {
//将globalState.allowStateChanges设置为false 意思是以下操作不会修改状态
//如果在reaction的读取操作中,未将响应值返回出来此处得不到值只会获取到undifined新值旧值一直相等就不会调用响应逻辑
const nextValue = allowStateChanges(false, () => expression(r))
changed = firstTime || !equals(value, nextValue)//用来判断新的值与旧的值是否相等是否发生改变
oldValue = value
value = nextValue
console.log(changed,'changed','r.track内部代码被执行')
})
if (firstTime && opts.fireImmediately!) {
//初始reaction并未执行 调用什么方法才能让if或者else执行,难道是autorun
effectAction(value, oldValue, r)
}
else if (!firstTime && changed){
//初始reaction并未执行,在set时会执行到这
effectAction(value, oldValue, r)
}
firstTime = false
}
r.schedule_()
这里expression(r),effectAction分别是reaction方法的第一参和第二参,它被放在r.track方法中执行,我们可以看出第一参只有在reaction被调用时执行,因为firstTime的限制第二参不会执行。
由 r.schedule_()调用这里reactionRunner方法
这里有一个重点方法便是r.track,顾名思义我们也大概猜出它是来干什么的。
track(fn: () => void) {
globalState.trackingContext = this
const result = trackDerivedFunction(this, fn, undefined)//reaction响应关键函数
}
track方法目前我们只关注上面两行代码globalState.trackingContext = this上面已经说过了,接下来便是
trackDerivedFunction
function trackDerivedFunction<T>(derivation: IDerivation, f: () => T, context: any) {
bindDependencies(derivation)
}
我们只关注bindDependencies
function bindDependencies(derivation: IDerivation) {
// invariant(derivation.dependenciesState !== IDerivationState.NOT_TRACKING, "INTERNAL ERROR bindDependencies expects derivation.dependenciesState !== -1");
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//调整为observableValue真实存在的数量
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_()
}
}
这里将完整代码展示出来,可以看出此方法的主要作用就是遍历此reaction存储的所有的observable,对于旧的且不响应的将它解除绑定,对于新的就添加绑定,这里我们看addObserver
function addObserver(observable: IObservable, node: IDerivation)
observable.observers_.add(node)//Set结合
}
由此断定了我们的猜想,在reaction内部逻辑中存在它将自己绑定在observable中
接下来我们便可以大胆猜测observable.setNewValue中主要的作用就是遍历observable中的reaction
并执行在reaction第二参,也就是用户自定义的响应时应该做什么的逻辑
响应的实现
直接看Observable.setNewValue
setNewValue_(newValue: T) {
const oldValue = this.value_
this.value_ = newValue
this.reportChanged()//触发reaction
if (hasListeners(this)) {
notifyListeners(this, {
type: UPDATE,
object: this,
newValue,
oldValue
})
}
}
这里我们只看reportChanged 会看前面的observable.get 你会发现它与get中reportObserved相对应,
这两个方法正是读取时注册依赖,写入时响应依赖
public reportChanged() {
startBatch()
propagateChanged(this)
endBatch()
}
这里注意下startBatch, endBatch,你会发现在mobx源码中很多地方用到它,startBatch内部逻辑很简单就一句 globalState.inBatch++,相应的 endBatch会执行globalState.inBatch--,而在endBatch中执行了响应
注startBatch, endBatch是mobx注释action的关键所在
我们先看下propagateChanged(this)
function propagateChanged(observable: IObservable) {
// invariantLOS(observable, "changed start");
if (observable.lowestObserverState_ === IDerivationState_.STALE_) return
observable.lowestObserverState_ = IDerivationState_.STALE_
// Ideally we use for..of here, but the downcompiled version is really slow...
observable.observers_.forEach(d => {
if (d.dependenciesState_ === IDerivationState_.UP_TO_DATE_) {
if (__DEV__ && d.isTracing_ !== TraceMode.NONE) {
logTraceInfo(d, observable)
}
d.onBecomeStale_()//这里调用Reaction.schedule_添加调用reaction的栈
}
d.dependenciesState_ = IDerivationState_.STALE_
})
// invariantLOS(observable, "changed end");
}
遍历observable.observers_内存储的Reaction,这和我们之前预想的setNewValue的执行逻辑吻合
注意 d.onBecomeStale_(),实际调用了,Reaction内部schedule_
onBecomeStale_() {
this.schedule_()
}
schedule_() {
if (!this.isScheduled_) {
this.isScheduled_ = true
globalState.pendingReactions.push(this)
runReactions()//它会用runReactionsHelper遍历globalState.pendingReactions内的Reaction并执行runReaction_
//调度调用此函数实际上会调用onInvalidate而在reaction方法中会调用track方法
}
}
globalState.pendingReactions.push(this) 嗯和在get时用globalState.trackingContext存储reaction方式一样,所以接下来会遍历globalState.pendingReactions内的reaction以执行自定义响应逻辑,来看 endBatch
function endBatch() {
if (--globalState.inBatch === 0) {
console.log('开始调用reaction')
runReactions()
// the batch is actually about to finish, all unobserving should happen here.
const list = globalState.pendingUnobservations
for (let i = 0; i < list.length; i++) {
const observable = list[i]
observable.isPendingUnobservation_ = false
if (observable.observers_.size === 0) {
if (observable.isBeingObserved_) {
// if this observable had reactive observers, trigger the hooks
observable.isBeingObserved_ = false
observable.onBUO()
}
if (observable instanceof ComputedValue) {
// computed values are automatically teared down when the last observer leaves
// this process happens recursively, this computed might be the last observabe of another, etc..
observable.suspend_()
}
}
}
globalState.pendingUnobservations = []
}
}
会看到只有当--globalState.inBatch === 0时runReactions(),所以runReactions
是可以延迟执行的
runReactions执行了runReactionsHelper
function runReactionsHelper() {
globalState.isRunningReactions = true
const allReactions = globalState.pendingReactions//由Reaction中schedule_添加Reaction
let iterations = 0
// While running reactions, new reactions might be triggered.
// Hence we work with two variables and check whether
// we converge to no remaining reactions after a while.
while (allReactions.length > 0) {
if (++iterations === MAX_REACTION_ITERATIONS) {
console.error(
__DEV__
? `Reaction doesn't converge to a stable state after ${MAX_REACTION_ITERATIONS} iterations.` +
` Probably there is a cycle in the reactive function: ${allReactions[0]}`
: `[mobx] cycle in reaction: ${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_()
}
globalState.isRunningReactions = false
}
显而易见遍历globalState.pendingReactions内的reaction并执行其runReaction_方法。到此便验证了我们之前预想的setNewValue的执行逻辑完全吻合
runReaction_方法
runReaction_() {
if (!this.isDisposed_) {
startBatch()
this.isScheduled_ = false
const prev = globalState.trackingContext
globalState.trackingContext = this
if (shouldCompute(this)) {
this.isTrackPending_ = true
try {
this.onInvalidate_()
if (__DEV__ && this.isTrackPending_ && isSpyEnabled()) {
// onInvalidate didn't trigger track right away..
spyReport({
name: this.name_,
type: "scheduled-reaction"
})
}
} catch (e) {
this.reportExceptionInDerivation_(e)
}
}
globalState.trackingContext = prev
endBatch()
}
}
这里我们只关注this.onInvalidate_(),这个是由实力化Reaction时第二参传入的,而这第二参便是在reaction内的reactionRunner
function reactionRunner() {//在set时会再度调用此方法 runReactionsHelper会调用Reaction中runReaction_方法进而调用这里
isScheduled = false
if (r.isDisposed_) return
let changed: boolean = false
r.track(() => {
//将globalState.allowStateChanges设置为false 意思是以下操作不会修改状态
//如果在reaction的读取操作中,未将响应值返回出来此处得不到值只会获取到undifined新值旧值一直相等就不会调用响应逻辑
const nextValue = allowStateChanges(false, () => expression(r))
changed = firstTime || !equals(value, nextValue)//用来判断新的值与旧的值是否相等是否发生改变
oldValue = value
value = nextValue
console.log(changed,'changed','r.track内部代码被执行')
})
if (firstTime && opts.fireImmediately!) {
//初始reaction并未执行 调用什么方法才能让if或者else执行,难道是autorun
effectAction(value, oldValue, r)
}
else if (!firstTime && changed){
//初始reaction并未执行,在set时会执行到这
effectAction(value, oldValue, r)
}
firstTime = false
}
此时因为reactionRunner已经不是第一次执行(第一次执行是在reaction方法调用时执行)所以就会执行 effectAction这是我们自定义的响应逻辑
reaction使用的示例代码,其中value=>{console.log(value,'获取响应')便是effectAction
reaction((r)=>{
console.log(testMobx.value,'测试全局state存入的reaction')
//未将响应值返回出来此处得不到值只会获取到undifined新值旧值一直相等就不会调用响应逻辑
return testMobx.value},value=>{console.log(value,'获取响应')/* 此方法自动被action包裹执行action名为Reaction */})
细心会发现,每次响应都会执行 r.track而 r.track是注册依赖,那么重复调用会不会造成reaction重复,
在bindDependencies方法中,每一个observable都有个属性diffValue_当它是0时证明不在响应,应该被丢弃,当它是1时证明是新的observable则添加,而我们此时的observable在一种尴尬的情境,它既不是新的,也不是不响应了。
所以bindDependencies先执行删除当diffValue_为0时执行removeObserver(dep, derivation),不为0时将它置为0,下面便执行添加,当是1时才添加,所以依赖不会重复注册。
代码详情
while (l--) {
const dep = prevObserving[l]
if (dep.diffValue_ === 0) {
removeObserver(dep, derivation)
}
dep.diffValue_ = 0
}
while (i0--) {
const dep = observing[i0]
if (dep.diffValue_ === 1) {
dep.diffValue_ = 0
addObserver(dep, derivation)
}
}
以上便是mobx响应系统实现的基本逻辑
mobx的其他需要学习的功能代码
action注释
根据上面所讲述mobx的基本响应过程,我们发现每次响应时,都会将与该observable所绑定的所有reaction遍历执行,注意看响应过程中的源码,它并不是简单将observable的reaction直接取出遍历,而是通过由Reaction中schedule_添加Reaction到globalState.pendingReactions。这样做的好处显而有一个,那就是去除了runReaction和observable直接的耦合,如果不这样那么当observable响应时,runReaction就必然会立刻调用
我们设想这样一种情况当在一个函数域中我们将一个observable修饰的属性接连改变两次或多次
increment() {
this.value++ //被observable修饰过的属性
...
this.value++
}
我们有时察觉不到同一个属性在一个函数域中调用两次,或是一种更常见的情景两个接连被调用的方法都改变了this.value的值,当然我们需要取最新的改变值。如果用上面所知道的基本流程推理,我们很容易知道,每一次改变都需要遍历reaction,而这种情况我们就需要重复的遍历多次,这样浪费性能,实际上我们仅需要遍历一次就足够了
上面所说globalState.pendingReactions的作用消除了observable和runReaction直接的耦合,这样最直接的好处就是当observable开始响应时,runReaction可以稍缓执行,假设直接遍历observable里头的reaction并执行其中runReaction是做不到这一点的。
所以action大概做了件什么事,也有数了,接下来看代码
function executeAction(
actionName: string,
canRunAsDerivation: boolean,
fn: Function,
scope?: any,
args?: IArguments
) {
const runInfo = _startAction(actionName, canRunAsDerivation, scope, args)
try {
return fn.apply(scope, args)
} catch (err) {
runInfo.error_ = err
throw err
} finally {
_endAction(runInfo)
}
}
这里fn是被action修饰的方法,如果翻到最前面的示例代码被action包裹的方法是increment
很明显的逻辑先是startAction然后执行我们被包裹的方法,最后endAction
等等没发现startAction和endAction组合方式貌似很眼熟么?
没错正是startBatch和endBatch。观看startAction源码发现其内部执行了startBatch,这也就意味着在其内部执行的任何响应都不会触发runReaction的调用,除非当action执行结束调用_endAction。
所以我们立马能想到在_endAction调用了endBatch,由于在action包裹的函数无论对被修饰属性做了多少写入操作,由于再执行endBatch时其判断 if (--globalState.inBatch === 0)都不会成立,也就不会执行runReaction,原因是action再其内部方法执行前先调用了startBatch让inBatch加了1。
这最直接的体现就是加入我们多次更改被修饰属性值,实际上只调用一次,这在绝大多数情况下都是我们想要的,因为实在没必要多次重复调用。
这就是action的作用,也是之前所说startBatch和endBatch是action核心的原因
对于复合类型array,set,map实现响应系统的方式
上面所讲的响应流程及其原理,拿最基本的类型的属性举例,实际上复合类型的属性要复杂些,但最终仍是走这套响应流程,所以先从最基本入手,才能更深入的去研究这类复合类型响应的实现。
我们最常见的复合类型就是数组了,实际上对于数组的处理方式和set,map一样,所以这里就只拿数组array举例。
我们现在简单设想一下对于被赋值为数组的属性,其响应和上述的基本响应一样,问题在于数组内部
对于数组我们希望的是,无论是其内部元素改变,或是数组长度发生改变都要去触发响应,这也可以说我们最长对数组操作的两种方法如下标赋值或是执行push都会触发响应。
所以如何实现呢?
由于数组是js中原生存在的数据结构,所以不能对其进行defineProperty,所以我们使用Proxy
首先我们知道响应系统的运行原理时读取时注入依赖,写入时响应依赖,分别对应两个关键方法reportObserved
和reportChanged
读取时注册依赖这点倒是容易实现,只要是数组的属性被读取我们就执行reportObserved
但如果数组执行push操作时,我们该如何响应呢?在执行push之前我们知道它会读取一次push属性,难道在读取时响应依赖?万一它仅仅只是读取,而不执行呢?
所以最简单的逻辑就是我们代理push方法。
简单的示例代码
push(value){
target.push(value)
reportChanged(this)
}
现在开始验证以上想法
我们知道修饰属性的注解observable会被ObservableObjectAdministration调用其make_方法然后调用extend_回到ObservableObjectAdministration.defineObservableProperty_,看过observable源码我们知道observable是个function但目前来看只调用了它make_方法和extend_那其其他功能属性再哪调用呢?
答案是在ObservableObjectAdministration.defineObservableProperty_内创建ObservableValue对象时调用,
关键代码
this.value_ = enhancer(value, undefined, name_)//enhancer是deepEnhancer
function deepEnhancer(v, _, name) {
// it is an observable already, done
if (isObservable(v)) return v
// something that can be converted and mutated?
if (Array.isArray(v)) return observable.array(v, { name })
if (isPlainObject(v)) return observable.object(v, undefined, { name })
if (isES6Map(v)) return observable.map(v, { name })
if (isES6Set(v)) return observable.set(v, { name })
if (typeof v === "function" && !isAction(v) && !isFlow(v)) {
if (isGenerator(v)) {
return flow(v)
} else {
return autoAction(name, v)
}
}
return v
}
这里enhancer调用了Observable其他功能属性(既是对各种类型的处理方式)如何是基本类型则直接返回,对于数组则直接调用createObservableArray(通过observable.array)
function createObservableArray<T>(
initialValues: T[] | undefined,
enhancer: IEnhancer<T>,
name = __DEV__ ? "ObservableArray@" + getNextId() : "ObservableArray",
owned = false
): IObservableArray<T> {
assertProxies()
const adm = new ObservableArrayAdministration(name, enhancer, owned, false)
addHiddenFinalProp(adm.values_, $mobx, adm)
const proxy = new Proxy(adm.values_, arrayTraps) as any
adm.proxy_ = proxy
if (initialValues && initialValues.length) {
const prev = allowStateChangesStart(true)
adm.spliceWithArray_(0, 0, initialValues)
allowStateChangesEnd(prev)
}
console.log(adm.values_,'看看数组信息')
return proxy
}
这里我们看到对数组使用Proxy并返回被代理后的数组,这里的我们先看arrayTraps
const arrayTraps = {
get(target, name) {
const adm: ObservableArrayAdministration = target[$mobx]
if (name === $mobx) return adm
if (name === "length") return adm.getArrayLength_()
if (typeof name === "string" && !isNaN(name as any)) {
return adm.get_(parseInt(name)) //数组下标的访问形式
}
if (hasProp(arrayExtensions, name)) {
return arrayExtensions[name]//数组方法的访问形式
}
return target[name]
},
set(target, name, value): boolean {
const adm: ObservableArrayAdministration = target[$mobx]
if (name === "length") {
adm.setArrayLength_(value)
}
if (typeof name === "symbol" || isNaN(name)) {
target[name] = value
} else {
// numeric string
adm.set_(parseInt(name), value)
}
return true
},
preventExtensions() {
die(15)
}
}
对数组get,set的设置,这里我们看arrayExtensions,看过源码(代码量太大这里就不贴了)我们会知道它类似于上述写的push方法一样,它对数组的所有方法都进行了代理实现,也就是说我们无论执行数组什么方法都会执行其提供的方法
在createObservableArray有最重要一点const adm = new ObservableArrayAdministration(name, enhancer, owned, false)
创建了ObservableArrayAdministration对象实际上arrayExtensions提供的方法都会调用ObservableArrayAdministration方法
class ObservableArrayAdministration
implements IInterceptable<IArrayWillChange<any> | IArrayWillSplice<any>>, IListenable {
atom_: IAtom
readonly values_: any[] = [] // this is the prop that gets proxied, so can't replace it!
interceptors_
changeListeners_
enhancer_: (newV: any, oldV: any | undefined) => any
dehancer: any
proxy_!: IObservableArray<any>
lastKnownLength_ = 0
...
}
这里我们看values_属性,他是只读的实际上对于数组的任何操作都是操作它
atom属性类型是IAtom
constructor(
name = __DEV__ ? "ObservableArray@" + getNextId() : "ObservableArray",
enhancer: IEnhancer<any>,
public owned_: boolean,
public legacyMode_: boolean
) {
this.atom_ = new Atom(name)
this.enhancer_ = (newV, oldV) =>
enhancer(newV, oldV, __DEV__ ? name + "[..]" : "ObservableArray[..]")
}
在实例时被赋值
Atom类型我们之间提到的ObservableValue便是继承于它,内部属性 observers_存储绑定的reaction,
两个关键方法reportObserved和reportChanged。
所以我们知道当对数组操作时都会由 ObservableArrayAdministration代理执行,然后恰当的使用this.atom_.reportObserved和this.atom_.reportChange来完成注册和响应。