Reading the source code for the first time, there may be some incorrect understandings. Hope the big guys can help me correct it. At the beginning, I looked at 6, and later I saw that observable found that there was a slight gap with 5, so there may be 6 source code in the "autorun" part, but the gap is not big.
1. The basic concept of mobx
Observable
Observer observation
Reaction
var student = mobx.observable({
name: '张三',
});
mobx.autorun(() => {
console.log('张三的名字:', student.name);
});
2. The principle of mobx
1. In the reactive function (such as the above autorun usually access one or more observable objects),
- 1) Autorun first creates an instance object reaction of type Reaction, and a callback function of a responsive function through the parameter track.
- 2) Then execute the reaction.schedule_ method, execute the callback function, and call the observer observable.get method in the callback function to trigger the reportObserved method.
- 3) In the reportObserved method, the observavle object will be collected in the globalState.trackingDerivation.newObserving_ queue (globalState.trackingDerivation is equivalent to the reaction object at this time)
- 4) Process the dependency between reaction and observable, traverse the reaction.newObserving_ attribute, and add the current reaction object to each observable.observers_ attribute in the newObserving_ queue.
2. When the value of the observable changes, the set method of the observable object is called to trigger the propagateChange method. In the propagateChange method, traverse the observable.observers_ property and execute the reaction.onBecomeStale method in turn, and execute the above 2) 3) 4) again.
3. Source code interpretation-autorun
The following is the deleted code
3.1 autorun
export function autorun(
view: (r: IReactionPublic) => any,// autoruan函数的回调函数
opts: IAutorunOptions = EMPTY_OBJECT
): IReactionDisposer {
const name: string = "Autorun"
const runSync = !opts.scheduler && !opts.delay
// 首先创建一个Reaction类型的对象 主要功能是用来控制任务的执行
let reaction = new Reaction(
name,
function (this: Reaction) {
this.track(reactionRunner)
},
opts.onError,
opts.requiresObservable
)
function reactionRunner() { view(reaction) } // view即autorun函数的回调
reaction.schedule_() // 立即执行一次部署
return reaction.getDisposer_() // 用于在执行期间清理 autorun
}
From the source code above, it can be seen that autorun mainly does three actions
- 1) The main function of creating a Reaction type object is to control the execution of the task
- 2) Assign view, the callback function of auto, to reaction.track
- 3) Execute a deployment immediately. At this time, you should understand what the document says "When using autorun, the provided function is always triggered immediately"
3.2 The source code of reaction.schedule_
schedule_() {
if (!this.isScheduled_) {
this.isScheduled_ = true
globalState.pendingReactions.push(this) // 当前的reaction对象入列
runReactions() // 队列中的所有reaction对象执行runReaction_方法
}
}
function runReactionsHelper() {
let remainingReactions = allReactions.splice(0)
for (let i = 0, l = remainingReactions.length; i < l; i++)
remainingReactions[i].runReaction_()
}
The schedule_ method does two things
- 1) The current reaction object is listed
- 2) All reaction objects in the queue execute the runReaction_ method
3.3 runReaction_ source code
runReaction_() {
startBatch() // 开启一层事务
this.isScheduled_ = false
if (shouldCompute(this)) {// derivation.dependenciesState_默认为-1 (未跟踪)
this.onInvalidate_()
}
endBatch() // 关闭一层事务 startBatch和endBatch总是成对出现
}
Looking at the code above, you can find that onInvalidate_ is passed into the constructor when the Reaction is initialized, and the reaction.track method is actually called
track(fn: () => void) {
startBatch()
...
const result = trackDerivedFunction(this, fn, undefined) // 执行任务 更新依赖
...
endBatch()
}
3.4 The track method mainly calls trackDerivedFunction
export function trackDerivedFunction<T>(derivation: IDerivation, f: () => T, context: any) {
...
globalState.trackingDerivation = derivation // 将derivation(此处等同于reaction对象)挂载到全局变量 这样其他成员也可访问此derivation
...
// 执行reaction传递的的回调方法,翻看代码可以看出执行的是autoran函数的回调方法
// 回调中一般会调用一或多个observable对象,触发observable.get方法,再触发reportObserved方法
let result = f.call(context)
...
globalState.trackingDerivation = prevTracking
bindDependencies(derivation) // 更新observable和raction的依赖关系
return result
}
Execution Because autorun calls back to the student.name variable, the "." here is actually the get operation; once the get operation is designed, the observer who supervises the attribute of this name will execute the reportObserved method (this will be highlighted when Oobservable is introduced later).
3.5 reportObserved source code
export function reportObserved(observable: IObservable): boolean {
...
const derivation = globalState.trackingDerivation
if (derivation !== null) {
if (derivation.runId_ !== observable.lastAccessedBy_) {
// 更被观察者的lastAccessedBy_属性(事务id),这个是为了避免重复操作
observable.lastAccessedBy_ = derivation.runId_
// 更新derivation(此处为reaction)的newObserving属性,将被观察者加入该队列中
// 后续derivation和observable更新依赖关系就靠这个属性
derivation.newObserving_![derivation.unboundDepsCount_++] = observable
...
}
return true
} else if (observable.observers_.size === 0 && globalState.inBatch > 0) {
queueForUnobservation(observable)
}
return false
}
The code above, we mainly focus on the operation that affects derivation
- 1) Update the lastAccessedBy_ attribute (transaction id) of observable, this is to avoid repeated operations.
- 2) Update the newObserving attribute of derivation (reaction here), add observable to the queue, and follow-up derivation and observable update dependencies rely on this attribute
After the execution of the autorun task is completed, the derivation starts to update the dependency relationship with the observable.
3.6 bindDependencies source code
function bindDependencies(derivation: IDerivation) {
const prevObserving = derivation.observing_
// derivation.newObserving_为derivation依赖的observable对象的队列
const observing = (derivation.observing_ = derivation.newObserving_!)
let lowestNewObservingDerivationState = IDerivationState_.UP_TO_DATE_ // 默认为0
let i0 = 0,
l = derivation.unboundDepsCount_
for (let i = 0; i < l; i++) {
/**
* 以下是一个去重的过程
* observable.diffValue_默认是0
* 循环时候置为1,因为observing为Observable类型的对象数组,所以不同位置上相同的值的diffValue_都会变成1
* 在遍历到重复项后就不会进入下边的判断,i0就不会++
* 遍历到非重复项(diffValue_为0的项),则直接将此项填充到i0对应的位置上
* 这样数组循环完毕,i0即非重复项的数量,observing.length = i0即删除掉了多余项
*/
const dep = observing[i]
if (dep.diffValue_ === 0) {
dep.diffValue_ = 1
if (i0 !== i) observing[i0] = dep
i0++
}
}
observing.length = i0
derivation.newObserving_ = null // newObserving 置空
/**
* prevObserving中和observing中存在的均为observable对象
* 此时如果在上边的循环完成后 observing存在的observable对象的diffValue_均为1
* 在prevObserving队列如果是diffValue_仍然为0,表示当前derivation已经不依赖此observable对象
*/
l = prevObserving.length
while (l--) {
const dep = prevObserving[l];
if (dep.diffValue_ === 0) {
// 将当前derivation已不再依赖此observable对象,将其从observable.observers_中的deleted掉
removeObserver(dep, derivation)
}
dep.diffValue_ = 0 // 将prevObserving队列中的observable的diffValue_均置为0
}
while (i0--) {
const dep = observing[i0]
// observing仍然为1的说明此observable对象不在prevObserving队列中
if (dep.diffValue_ === 1) {
dep.diffValue_ = 0
// 在observable.observers_中添加当前的derivation对象
addObserver(dep, derivation)
}
}
// 通过以上的3次循环,将derivation.observing更新为最新的依赖(并去重),
// 并在已经不依赖的observable对象的observers_中delete当前的derivation对象
// 在新建立起的依赖的observable对象的observers_中add当前的derivation对象
}
Respond to changes in the value of the observable object
As mentioned above, once the value of observable changes, the observable.get method will be triggered, and then the propagateChange method will be triggered. The source of the propageateChange is as follows
export function propagateChanged(observable: IObservable) {
...
observable.observers_.forEach(d => {
...
d.onBecomeStale_()
...
})
}
observable.observers_ stores derivation objects that are dependent on observable objects. In the propagateChanged method, traverse the observers_ execute the onBecomeStale_ method of the derivation object. Let’s take a look at the source code of onBecomeStale_
3.7 The source code of onBecomeStale_
onBecomeStale_() {
this.schedule_()
}
Is this.schedule_ very familiar? Looking at the code above, I found that reaction.schedule_() was called when the reaction object was created in the autorun function. So now I understand that propagateChanged calls onBecomeStale_ to make the reaction execute the previous deployment operation again (that is, execute the autorun callback to handle the dependencies);
4. Next, look at the observable (observable) part
4.1 alias of observable createObservable
export const observable: IObservableFactory &
IObservableFactories & {
enhancer: IEnhancer<any>
} = createObservable as any // observable的别名createObservable
// 将observableFactories的属性复制一份给observable
Object.keys(observableFactories).forEach(name => (observable[name] = observableFactories[name]))
- 1) First of all, observable is the same function as createObservable.
- 2) observable replicates the properties of observableFactories.
function createObservable(v: any, arg2?: any, arg3?: any) {
// @observable someProp;
if (typeof arguments[1] === "string" || typeof arguments[1] === "symbol") {
return deepDecorator.apply(null, arguments as any)
}
// it is an observable already, done
if (isObservable(v)) return v
// something that can be converted and mutated?
const res = isPlainObject(v)
? observable.object(v, arg2, arg3)
: Array.isArray(v)
? observable.array(v, arg2)
: isES6Map(v)
? observable.map(v, arg2)
: isES6Set(v)
? observable.set(v, arg2)
: v
// this value could be converted to a new observable data structure, return it
if (res !== v) return res
}
The createObservable method plays the role of forwarding, forwarding the incoming object to the specific conversion function.
Briefly analyze the specific conversion mode
- 1) arguments[1] === "string" || typeof arguments[1] === "symbol" uses the decorator @observable, the decorator's parameters (target, prop, descriptor) are also That is, prop is the attribute name and the string type
- 2) isObservable(v) has been converted to an observation value, no need to convert again
- 3) Observable.object, observable.array, observable.map, observable.set call specific conversion methods according to the type of the input parameter
- 4) Prompt users to suggest using observable.box method for primitive types
4.2 observable.box
observable.box is described in the documentation.
observable.box converts ordinary values into observable values, as in the following example.
const name = observable.box("张三");
console.log(name.get());
// 输出 '张三'
name.observe(function(change) {
console.log(change.oldValue, "->", change.newValue);
});
name.set("李四");
// 输出 '张三 -> 李四'
observable.box retrun An object of type ObservableValue.
box<T = any>(value?: T, options?: CreateObservableOptions): IObservableValue<T> {
const o = asCreateObservableOptions(options) // 格式化入参
// ObservableValue的拥有方法get set observe intercept...
return new ObservableValue(value, getEnhancerFromOptions(o), o.name, true, o.equals)
},
The "name.set("Li Si")" in the case is to call the set method of ObservableValue. When I introduce ObservableValue later, I will focus on it.
4.3 The core class ObservableValue
ObservableValue inherits the Atom atomic class. First, I will sort out the main capabilities of Atom and ObservableValue.
Atom
public reportObserved(): boolean {
return reportObserved(this)
}
public reportChanged() {
startBatch()
propagateChanged(this)
endBatch()
}
ObservableValue
public set(newValue: T) {
const oldValue = this.value
newValue = this.prepareNewValue(newValue) as any
if (newValue !== globalState.UNCHANGED) {
const oldValue = this.value
this.value = newValue
this.reportChanged()
...
}
}
public get(): T {
this.reportObserved()
return this.dehanceValue(this.value)
}
intercept
observe
Among them, reportObserved and propagateChanged were introduced when sorting out autorun.
- 1) reportObserved: Calling the observation value is used to update the dependency between derivation and observable.
- 2) propagateChanged: When the observation value changes, the derivation stored in the observers of the observable object will execute the onBecomeStale method to re-execute the deployment operation.
- 3) Observablevalue's set modifies value and Atom's reportChanged method is called to trigger propagateChanged at the same time.
- 4) Observablevalue's get gets the value value while calling Atom's reportObserved method to trigger reportObserved.
So in the above case, "name.set("李四");" will trigger the propagateChanged method, which will execute the dependent derivation and re-execute the deployment operation
Next, let’s take a look at what did new ObservableValue do?
constructor(
value: T,
public enhancer: IEnhancer<T>,
public name = "ObservableValue@" + getNextId(),
notifySpy = true,
private equals: IEqualsComparer<any> = comparer.default
) {
...
this.value = enhancer(value, undefined, name)
}
The constructor of ObservableValue calls the enhancer to process the value. The enhancer uses the parameter to create an ObservableValue type object and the passed parameter getEnhancerFromOptions(o). getEnhancerFromOptions returns deepEnhancer by default.
function getEnhancerFromOptions(options: CreateObservableOptions): IEnhancer<any> {
return options.defaultDecorator
? options.defaultDecorator.enhancer
: options.deep === false
? referenceEnhancer
: deepEnhancer
}
The main content of gdeepEnhancer is as follows.
export function deepEnhancer(v, _, name) {
if (isObservable(v)) return v
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 })
return v
}
Does this deepEnhancer look a bit familiar? If you look up, you can see that it is very similar to the createObservable function, which plays a role of forwarding, and forwards the incoming object to a specific conversion function. So to understand observable we mainly need to understand these conversion functions. Next we mainly analyze observable.object.
4.4 observable.object
object<T = any>(
props: T,
decorators?: { [K in keyof T]: Function },
options?: CreateObservableOptions
): T & IObservableObject {
const o = asCreateObservableOptions(options)
if (o.proxy === false) {
return extendObservable({}, props, decorators, o) as any
} else {
const defaultDecorator = getDefaultDecoratorFromObjectOptions(o)
const base = extendObservable({}, undefined, undefined, o) as any
const proxy = createDynamicObservableObject(base)
extendObservableObjectWithProperties(proxy, props, decorators, defaultDecorator)
return proxy
}
}
When o.proxy is true, there is only one more Proxy, and the rest of the work is basically similar, so the main focus is on the extendObservable method.
In extendObservable, the methods mainly use getDefaultDecoratorFromObjectOptions, asObservableObject, and extendObservableObjectWithProperties. Because getDefaultDecoratorFromObjectOptions is related to extendObservableObjectWithProperties, let's look at asObservableObject first, and then look at the other two methods.
4.5 extendObservable
export function extendObservable<A extends Object, B extends Object>(
target: A,
properties?: B,
decorators?: { [K in keyof B]?: Function },
options?: CreateObservableOptions
): A & B {
options = asCreateObservableOptions(options)
const defaultDecorator = getDefaultDecoratorFromObjectOptions(options) // 默认返回deepDecorator装饰器
asObservableObject(target, options.name, defaultDecorator.enhancer) // make sure object is observable, even without initial props
if (properties)
extendObservableObjectWithProperties(target, properties, decorators, defaultDecorator)
return target as any
}
4.6 asObservableObject
asObservableObject method:
- 1) Create an object amd as an instance of the ObservableObjectAdministration class.
- 1) amd assigned to target[$mobx]
- 2) Return to amd;
export function asObservableObject(
target: any,
name: PropertyKey = "",
defaultEnhancer: IEnhancer<any> = deepEnhancer
): ObservableObjectAdministration {
const adm = new ObservableObjectAdministration(
target,
new Map(),
stringifyKey(name),
defaultEnhancer
)
addHiddenProp(target, $mobx, adm)
return adm
}
4.7 extendObservableObjectWithProperties
extendObservableObjectWithProperties: loop the original object, and process each property value through the decorator function (decorators method is the default deepDecorator obtained through the getDefaultDecoratorFromObjectOptions method, so look directly at deepDecorator once)
export function extendObservableObjectWithProperties(
target,
properties, // 原对象
decorators,
defaultDecorator
) {
startBatch()
const keys = ownKeys(properties)
// 循环原对象
for (const key of keys) {
const descriptor = Object.getOwnPropertyDescriptor(properties, key)!
const decorator =
decorators && key in decorators
? decorators[key]
: descriptor.get
? computedDecorator
: defaultDecorator
const resultDescriptor = decorator!(target, key, descriptor, true) // 经过装饰器处理
if (
resultDescriptor // otherwise, assume already applied, due to `applyToInstance`
)
Object.defineProperty(target, key, resultDescriptor)
}
endBatch()
}
4.8 decorator
The decorator defaults to deepDecorator, let’s take a look at what it does.
export function createDecoratorForEnhancer(enhancer: IEnhancer<any>): IObservableDecorator {
const decorator = createPropDecorator(
true,
(
target: any,
propertyName: PropertyKey,
descriptor: BabelDescriptor | undefined,
_decoratorTarget,
decoratorArgs: any[]
) => {
const initialValue = descriptor
? descriptor.initializer
? descriptor.initializer.call(target)
: descriptor.value
: undefined
// 调用target[$mobx].addObservableProp方法
asObservableObject(target).addObservableProp(propertyName, initialValue, enhancer)
}
)
const res: any = decorator
res.enhancer = enhancer
return res
}
4.9 addObservableProp method
The target[$mobx].addObservableProp method is called in the decorator
addObservableProp(
propName: PropertyKey,
newValue,
enhancer: IEnhancer<any> = this.defaultEnhancer
) {
const { target } = this
if (hasInterceptors(this)) {
// 拦截处理
const change = interceptChange<IObjectWillChange>(this, {
object: this.proxy || target,
name: propName,
type: "add",
newValue
})
if (!change) return // 拦截器返回空的时候不需要重新忽略此次修改。
newValue = (change as any).newValue
}
// newValue转换成ObservableValue类型
const observable = new ObservableValue(
newValue,
enhancer,
`${this.name}.${stringifyKey(propName)}`,
false
)
this.values.set(propName, observable) // 存储
newValue = (observable as any).value
// generateObservablePropConfig方法返回以下描述符
// { ..., get() { return this[$mobx].read(propName) }, set(v) { this[$mobx].write(propName, v) } }
Object.defineProperty(target, propName, generateObservablePropConfig(propName)) // target生成propName属性
const notify = hasListeners(this)
const change = {
type: "add",
object: this.proxy || this.target,
name: propName,
newValue
}
this.keysAtom.reportChanged() // this.keysAtom即Atom的实例
}
addObservableProp method
- 1) Call the ObservableValue class to convert newValue into an observable value (Do you remember that the ObservableValue call above called the observable.object method through the enhancer. Now you can see that the ObservableValue is called again in the observable.object method when the properties of the object are looped. In this recursive way, the properties of the object are converted into observable values)
- 2) Store the attribute key and observable in target[$mobx].values
- 3) Add the attribute value of the original object to the target, and directly call the this[mobx].read and this[mobx].write methods through the get and set in the descriptor.
- 4) Call the reportChanged of the atomic class Atom to re-execute the deployment operation of the derivation that relies on this observable object.
In summary, the role of extendObservableObjectWithProperties is to loop the original object. Perform the above 4 steps to realize the proxy of the original object's properties to the target, and convert the value to an observable value, which is stored in target[$mobx].values.
4.10 read and write
read(key: PropertyKey) {
return this.values.get(key)!.get()
}
// observable.get方法
public get(): T {
this.reportObserved() // Atom下的reportObserved
return this.dehanceValue(this.value)
}
The read method will look up from this.values according to the attribute name, get the corresponding observable object and then call the observable.get method to trigger reportObserved
write(key: PropertyKey, newValue) {
const instance = this.target
const observable = this.values.get(key)
// intercept
if (hasInterceptors(this)) {
const change = interceptChange<IObjectWillChange>(this, {
type: "update",
object: this.proxy || instance,
name: key,
newValue
})
if (!change) return
newValue = (change as any).newValue
}
newValue = (observable as any).prepareNewValue(newValue)
if (newValue !== globalState.UNCHANGED) {
(observable as ObservableValue<any>).setNewValue(newValue)
}
}
// observable.prepareNewValue和observable.setNewValue方法
private prepareNewValue(newValue): T | IUNCHANGED {
if (hasInterceptors(this)) {
const change = interceptChange<IValueWillChange<T>>(this, {
object: this,
type: "update",
newValue
})
if (!change) return globalState.UNCHANGED
newValue = change.newValue
}
// apply modifier
newValue = this.enhancer(newValue, this.value, this.name) // 调用enhancer转换为可观察模式
return this.equals(this.value, newValue) ? globalState.UNCHANGED : newValue
}
setNewValue(newValue: T) {
const oldValue = this.value
this.value = newValue
this.reportChanged()
if (hasListeners(this)) {
notifyListeners(this, {
type: "update",
object: this,
newValue,
oldValue
})
}
}
write method
- 1) Call the observable.prepareNewValue method to convert the new value
- 2) Call observable.setNewValue to re-modify the value
- 3) Trigger the reportChanged method.
4.11 Summary
var student = mobx.observable({
name: '张三',
});
Mobix uses the target to proxy the incoming object through the observable method and assign it to the student.
Therefore, the structure of the student should be as follows
Triggered when student.name is called, get=>read=>observableValue.get=>reportObserved
When modifying set=>write=>observableValue.setNewValue=>reportChanged
Now you can basically understand the relationship between observable and autoruan.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。