“This is the 41st day of my participation in the First Challenge 2022.

Previously on & Background

This essay describes how Vue handles the custom component createComponent. The most important thing to understand here is that child components are not created directly by Vue, but by subclasses of Vue;

In our daily development, we write.vue files as an example. In script, we export just one option object, but even if we write multiple components, they do not affect each other, because multiple components are multiple instances, and instances are naturally isolated from each other. Instantiation is created by the constructor, which is a subclass of the extension of the option object we wrote;

Init, prepatch, Insert, and destroy. Init is responsible for the initialization of subcomponent instances. This process is equivalent to the whole process of new Vue. The process of creating template compilation and rendering functions that contain subcomponents;

Today we talk about the next topic — rendering Watcher. This article will focus on the dependency collection of data from templates in rendering Watcher and Vue.

Second, the Vue. Prototype. $mount

Var.prototype.$mount was rewritten to include the render function before mounting. This logic is not available in the non-compiled version.

// Cache the original $mount, which does not contain the logic to compile the template into a rendering function
const mount = Vue.prototype.$mount

// Override the $mount method
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
) :Component {
  if(! options.render) {if (template) {
    
      const{ render, staticRenderFns } = compileToFunctions(template, {.. },this)

      // Put two rendering functions on vm.$options,
      $options for vue.prototype. _render
      options.render = render
      options.staticRenderFns = staticRenderFns
    }
  }
  // Execute the mount, which will be the focus of today
  // Mount is vue.prototype.$mount
  return mount.call(this, el, hydrating)
}
Copy the code

Three, mount method

$mount = Vue. Prototype $mount;

Methods location: SRC/platforms/web/runtime/index. The js – > Vue. Prototype. $mount

Method parameters:

  1. el, mount point element in the pagediv#app
  2. hydrating: Ignore him

The mountComponent () method creates the render Watcher, which executes the render function to render elements to the page.

Vue.prototype.$mount = function ( el? : string | Element, hydrating? : boolean ): Component { el = el && inBrowser ? query(el) : undefined return mountComponent(this, el, hydrating) }Copy the code

Third, mountComponent

Methods location: SRC/core/instance/lifecycle. Js – > function mountComponent

Method parameters:

  1. vm.Component.
  2. elMount elements in the pagediv#app
  3. hydrating, ignore it

Methods:

  1. The triggerbeforeMountLifecycle hooks;
  2. The statementupdateComponentMethod,updateComponentIs used to pass toRender the watcherMethod within that methodvm._rendermethodVirtual DOMAnd pass tovm._updateMethods into thepatchStage; whenRender the watcherThe dependent data changes when thisupdateComponentIt’s also called to update the view;
  3. That’s what we got in the previous stepupdateComponentMethod to createRender the watcherInstance,Render the watcherAnd that’s what we used in the data reactive formWatcherSame thing, bothWatcherExamples of, but hisexpOrFnIs responsible for renderingupdateComponentMethods;
  4. Trigger manual mount yesmountedLifecycle hooks;
export function mountComponent (vm: Component, el: ? Element, hydrating? : boolean) :Component {
  vm.$el = el
  if(! vm.$options.render) { vm.$options.render = createEmptyVNodeif(process.env.NODE_ENV ! = ='production') { // No render function throws warning}
  }
  callHook(vm, 'beforeMount')

  let updateComponent
 
  if(process.env.NODE_ENV ! = ='production' && config.performance && mark) {
    // ...
  } else {
    // updateComponent is the core
    updateComponent = () = > {
      // Execute vm._render() to get a virtual VNode.
      // Pass the VNode to the vm._update method
      vm._update(vm._render(), hydrating)
    }
  }


  // This thing is rendering watcher
  // When initializing the render watcher, the constructor of the Watcher class decides,
  // To render watcher, mount watcher to the vm instance: vm._watcher
  // This is done because rendering watcher initialization is possible
  // The $forceUpdate method may be called when it is called,
  // In the mounted lifecycle hook of a child component,
  // The dependency vm._watcher is already defined
  new Watcher(vm, updateComponent, noop, {
    before () {
      if(vm._isMounted && ! vm._isDestroyed) { callHook(vm,'beforeUpdate')}}},true /* true indicates that the watcher is rendering watcher */)
  hydrating = false

  // Manually mount the instance to call the VM mounted hook
  // The mounted hook of the child component created by the render function will be called in the inserted hook of the component
  Inserted Hook is the previous createComponent installComponentHooks
  // Add hooks to the component's data, including init, prepatch, insert, destroy hooks
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')}return vm
}
Copy the code

3.1 updateCompnent

UpdateComponent is an intermediate method, used as the expOrFn parameter for creating render Watcher, this parameter is the method to be executed when watcher is evaluated; For rendering Watcher, evaluation is the operation of mounting the virtual DOM to the page.

let updateComponent
/ /...
updateComponent = () = > {
  // Execute the vm._render() function to get the virtual VNode
  // VNode passes the vm._update method to patch
  vm._update(vm._render(), hydrating)
}
Copy the code

3.2 Create render Watcher

new Watcher(
  vm, 
  updateComponent,
  noop,
  {
    before () {
      if(vm._isMounted && ! vm._isDestroyed) { callHook(vm,'beforeUpdate')}}},true /* Render watcher identifier */
)
Copy the code

3.3 see Watcher

export default class Watcher {
  // ...
  constructor (
    vm: Component, // corresponds to the VM passed above
    expOrFn: string | Function.// updateComponent above
    cb: Function.// noop is ignored and is an empty function
    options?: ?Object.{before () {... }} objectisRenderWatcher? : boolean// Whether to render watcher, which corresponds to true above
  ) {
    this.vm = vm
    if (isRenderWatcher) {
      // If it is a render Watcher then mount it to the VM
      // The mountComponent mentioned above is intended for a child component that calls vm.$forceUpdate
      vm._watcher = this
    }
    vm._watchers.push(this)
    // options
    if (options) {
      // Mount the passed before method to the current watcher instance
      this.before = options.before
    } else {
      // ...
    }
  
    if (typeof expOrFn === 'function') {
      // expOrFn is the updateComponent method
      this.getter = expOrFn
    } else {
      / /...
    }
    
    // Evaluate watcher, expOrFn,
    // For rendering Watcher, updateComponent
    // this.lazy We've only seen true in computed attributes, usually false
    this.value = this.lazy 
      ? undefined
      : this.get() // The get method is below, which calls the this.getter method
  }


  // Execute this.getter, which is the updateComponent method
  Getter is the updateComponent passed when watcher is instantiated
  // Collect dependencies in the template
  get () {
    // Open dep. target, dep. target = this, this is an instance of Watcher
    // This place is used for collecting dependencies below
    pushTarget(this)
    let value
    const vm = this.vm
    try {
      // Execute the callback function, such as updateComponent, to enter the patch phase
      value = this.getter.call(vm, vm)
    } catch (e) {
     
    } finally {
      if (this.deep) {
        traverse(value)
      }
      // Dep.target = null
      popTarget()
      this.cleanupDeps()
    }
    return value
  }
  //....
}
Copy the code

3.3.1 Watcher. Get & Dependency collection

We talked about Watcher earlier when we talked about data responsiveness, but why do we need to talk about it again? This is because the focus is different, and here we are talking about rendering Watcher.

Bind :xx/v-bind:xx/{{xx}}; bind:xx/ v-bind:xx/{{xx}} This step is crucial in explaining why the page is updated after the data is updated.

First of all, let’s outline the call process:

Getter = updateComponent -> this.get -> this.getter Equivalent to updateComponentCopy the code
  • updateComponentJust do one simple thing, callvm._render()getVNodeAnd then pass it tovm._update()Method, the operation goes intopatchStage, so calledpatchUpdate (new in this case)v-domTree, and mount it to the page
updateComponent = () = > {
  vm._update(vm._render(), hydrating)
}
Copy the code

Vm._render () calls the render function we got earlier…

Of course, it takes time before the render function is called, so let’s talk about dependency collection first.

Let’s look at some render function code:

/ / processing < span
// v-for="item in someArr"
// :key="index">{{item}}
// This is an example
function () {
  return with (this) {
      _l( 
      (someArr), // someArr is data on data
      function (item) {
        return _c(
          'span',
          { key:index },
          [
            _v(_s(item))
          ]
         )
      }
    )
  }
}
Copy the code

This. SomeArr (with this) accesses the Vue instance this.somearr, SomeArr = someArr; this.somearr = someArr; this.somearr = someArr; this.somearr = someArr;

Getters and setters (someArr); getters (someArr); getters (someArr);

export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function, shallow? : boolean) {

  // Create a Dep instance, which will be used by depend on the Dep below
  const dep = new Dep()
 
  Object.defineProperty(obj, key, {
    enumerable: true.configurable: true.// get intercepts the obj[key] read operation
    get: function reactiveGetter () {
     
      const value = getter ? getter.call(obj) : val
    
      if (Dep.target) {
        // Depending on the collection, add watcher to the DEP and add deP to the watcher
        dep.depend()
      }
      return value
    },

    // set intercepts the set operation on obj[key]
    set: function reactiveSetter (newVal) {}})}Copy the code

3.3.2 Dep. depend & Depend collection

PushTarget (this) in wather.prototype. get is combined with Dep. depend in Object.defineProperty.

export default class Dep {
  // dep. target is pushTarget(watcher)
  // Add deP to watcher
  // Put the observer into the observed
  depend () {
    if (Dep.target) {
      / / Dep. Target. AddDep is
      // watcher.prototype. addDep is simply an instance of Dep
      // Where did you get it? Const dep = new dep () in defineProperty
      Dep.target.addDep(this)}}// Add watcher to deP
  addSub (sub: Watcher) {
    this.subs.push(sub)
  }
}
Copy the code

3.3.3 wathcer.adddep & dependency collection

As you can see from the defineReactive method above, each data defined as reactive has a DEP instance that is then executed via ep.depend, Ep. denpend calls dep.target.addDep (wather.prototype. addDep)

The addDep method does this:

  1. Take this datadepAdded to thewatcher.newDepsIn the array;
  2. throughdep.pushTo put thiswatcherAdded to thedep.subs;
export default class Watcher {
  // ...
 
  // Two things:
  // 1. Add dep to self watcher
  // 2. Add yourself (watcher) to dep
  addDep (dep: Dep) {
    If the deP already exists, do not add it again
    const id = dep.id
    if (!this.newDepIds.has(id)) {
      // Cache dep.id for weight determination
      this.newDepIds.add(id)
      this.newDeps.push(dep)

      // Avoid repeating watcher in deP,
      // This. DepIds is set in the cleanupDeps method
      if (!this.depIds.has(id)) {
        // Add Watcher itself to the deP
        dep.addSub(this)}}}}Copy the code

Four,

The mount method is the core of the mountComponent method. This method creates the render Watcher. We reviewed the Render Watcher class and the Dep class.

  1. First of all, when we initialize the data in responsedata.someArrthroughdefineReactiveIt becomes a responsive data structure, so it becomes agetter/setterAnd finally thesomeArrThe agent tovmOn;
  2. We used an example above where the render function references asomeArrData for list rendering. performRender functiontheRender the watcherTo perform thePushTarget (rendering watcher),Dep.targetThis is theRender the watcher.
  3. Then to render the list you need accesssomeArrThe value ofsomeArrgetter;
  4. getterjudgmentDep.targetThe value of, and then thedepIn thewatcher.newDepsArray, anddepwillRender the watcherIn thedep.subsIn the array.
  5. If the data changes,depIs responsible for tellingRender the watcher, data changed, callwatcher.updateMethod reevaluate, of course, that’s a later story;