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.