3
头图
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.
下载 1.jpeg
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.


LeapFE
1.1k 声望2.3k 粉丝

字节内推,发送简历至 zhengqingxin.dancing@bytedance.com