First look at the following code:

class Demo {
  @observable
  public test = 1

  log: () = > void = autorun(() = > {
    console.log(`test input onChange: The ${this.test}`)})}Copy the code

Changing the value of test triggers automatic execution of the log function. The equivalent of passing an Autorun method automatically collects changes in the dependent Observable. My guess is that the Autorun function works like this

function autorun (fn) { 
   // Rely on collection preparation
   fn() // Trigger the Get method of the Observable property
   // Clean up dependency collection
}
Copy the code

Here’s a quick look at the autorun source code (5.15.4)

function autorun(
    view: (r: IReactionPublic) => any,
    opts: IAutorunOptions = EMPTY_OBJECT
) :IReactionDisposer {
 
    const name: string = (opts && opts.name) || (view as any).name || "Autorun@" + getNextId()
    construnSync = ! opts.scheduler && ! opts.delaylet reaction: Reaction
    
    // Look only at the autorun of synchronization. Asynchrony is based on the delay setTimeout passed in
    if (runSync) {
        // normal autorun
        reaction = new Reaction(
            name,
            // The reaction runner is called by the reaction runner. The reaction runner is called by the reaction runner. The reaction runner is called by the reaction runner
            function(this: Reaction) {
                this.track(reactionRunner)
            },
            opts.onError,
            opts.requiresObservable
        )
    } else {
       / /... Deal with asynchronous
    }

    function reactionRunner() {
        view(reaction)
    }
    / / put reaction in global globalState pendingReactions queue, it will perform runReactions
    reaction.schedule()
    // Return to unsubscribe
    return reaction.getDisposer()
}
Copy the code

Take a look at runReactions, which rely on the collection startup method

let reactionScheduler: (fn: () => void) = > void = f= > f();

function runReactions() {
  // Not in the transaction and no reaction is being performed
  if (globalState.inBatch > 0 || globalState.isRunningReactions) return
  // the core call runReactionsHelper
  reactionScheduler(runReactionsHelper)
}
Copy the code

RunReactionsHelper:

function runReactionsHelper() {
    globalState.isRunningReactions = true
    const allReactions = globalState.pendingReactions
    let iterations = 0

    . / / to iterate through all globalState pendingReactions of reaction, and perform runReaction of each object
    while (allReactions.length > 0) {
        if (++iterations === MAX_REACTION_ITERATIONS) {
            console.error(
                `Reaction doesn't converge to a stable state after ${MAX_REACTION_ITERATIONS} iterations.` +
                    ` Probably there is a cycle in the reactive function: ${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
}
Copy the code

The key to the runReaction is to trigger the onInvalidate parameter function, which is the view function wrapped in track (autorun’s passed function).

    // fn is the view function
    track(fn: () => void) {
   
        startBatch()
        ....
        this._isRunning = true
        // trackDerivedFunction is the core, pass fn to trackDerivedFunction,
        const result = trackDerivedFunction(this, fn, undefined)
        this._isRunning = false. endBatch() }Copy the code

TrackDerivedFunction is the method that finally calls autorun’s incoming function. This completes the get method that fires the Observable property. This is followed by a call to the GET method that listens to the Observable property, which completes dependency collection. For example, the Mobx ObservableValue class has a get method that traps the Observable property:

  public get(): T {
        // reportObserved Reports Observable to the derivation (reaction) of dependencies being collected
        . / / derivation from globalState trackingDerivation, globalState. TrackingDerivation mentioned above in the final trigger autorun incoming
       // trackDerivedFunction
        this.reportObserved()
        return this.dehanceValue(this.value)
    }
Copy the code

This completes dependency collection. The set method of the obserable property triggers the set method. If the value changes, the mobx will notify the dependence of the obserable property:

    public set(newValue: T) {
        const oldValue = this.value
        newValue = this.prepareNewValue(newValue) as any
        if(newValue ! == globalState.UNCHANGED) { ...// When the value changes, it triggers setNewValue, which eventually triggers onBecomeStale in Reaction. OnBecomeStale calls this.schedule()
            this.setNewValue(newValue)
            if(notifySpy && process.env.NODE_ENV ! = ="production") spyReportEnd()
        }
    }
Copy the code

Finally, we can see that the Reaction layer of MobX is cleverly designed. It not only removes the logic of dependency collection to manage the global dependency management, but also removes the side effect of different dependency management phases. When the dependency collection is initialized, we can set the track GET method. Reaction can also be used elsewhere, such as React, to update components while relying on updates. It seems that Vue3’s spin-off of Reactivity is a similar design idea.