Re-learn Vue source code, according to Huang Yi big man Vue technology revealed, one by one, consolidate the Vue source knowledge points, after all, chewed up is their own, all the articles are synchronized in the public number (road in the front stack) and github.

This above the graph example, through the lifecycle of the Vue function, the code in the SRC/core/instance/lifecycle. Js:

export function callHook (vm: Component, hook: string) {
  // #7573 disable dep collection when invoking lifecycle hooks
  pushTarget()
  const handlers = vm.$options[hook]
  if (handlers) {
    for (let i = 0, j = handlers.length; i < j; i++) {
      try {
        handlers[i].call(vm)
      } catch (e) {
        handleError(e, vm, `${hook} hook`)}}}if (vm._hasHookEvent) {
    vm.$emit('hook:' + hook)
  }
  popTarget()
}
Copy the code

Two parameters are passed in: Handlers are an array of created, created, created, created, created, created, created, created, created, created, created, created, created, created, created, created, created, created, and created. Each element in the array is a function, and the current context VM is passed to the Handlers via Call so that this in the page points to the current vUE instance. Let’s take a look at where the callHook is executed.

BeforeCreate and created

The first is init initialization:

Vue.prototype._init = function (options? :Object) {
  // ..
  initLifecycle(vm)
  initEvents(vm)
  initRender(vm)
  callHook(vm, 'beforeCreate')
  initInjections(vm) // resolve injections before data/props
  initState(vm)
  initProvide(vm) // resolve provide after data/props
  callHook(vm, 'created')
  // ...
}
Copy the code

When beforeCreated is executed, only the initialization life cycle, events and rendering are performed. At this time, no data can be retrieved. Created is created after the injection, data, and provider are initialized, so the data is available.

BeforeMount and mounted

MountComponent (vm._update(vm._render(),hydrating), Watcher, etc.);

export function mountComponent (vm: Component, el: ? Element, hydrating? : boolean) :Component {
  vm.$el = el
  // ...
  callHook(vm, 'beforeMount')

  let updateComponent
  /* istanbul ignore if */
  if(process.env.NODE_ENV ! = ='production' && config.performance && mark) {
    updateComponent = () = > {
      const name = vm._name
      const id = vm._uid
      const startTag = `vue-perf-start:${id}`
      const endTag = `vue-perf-end:${id}`

      mark(startTag)
      const vnode = vm._render()
      mark(endTag)
      measure(`vue ${name} render`, startTag, endTag)

      mark(startTag)
      vm._update(vnode, hydrating)
      mark(endTag)
      measure(`vue ${name} patch`, startTag, endTag)
    }
  } else {
    updateComponent = () = > {
      vm._update(vm._render(), hydrating)
    }
  }

  // we set this to vm._watcher inside the watcher's constructor
  // since the watcher's initial patch may call $forceUpdate (e.g. inside child
  // component's mounted hook), which relies on vm._watcher being already defined
  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted) {
        callHook(vm, 'beforeUpdate')}}},true /* isRenderWatcher */)
  hydrating = false

  // manually mounted instance, call mounted on self
  // mounted is called for render-created child components in its inserted hook
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')}return vm
}
Copy the code

It performs a beforeMount hook before vm._render() renders VNode. There is logic in new Watcher that if vm.$VNode is empty, the current VM has no parent node, which means the current node is the root node. Mounted invokeInsertHook() : / / mounted invokeInsertHook() : / / mounted invokeInsertHook()

export function createPatchFunction (backend) {
  // ...
  return function patch (oldVnode, vnode, hydrating, removeOnly) {
   // ...
    invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
    return vnode.elm
  }
}
Copy the code

Take a look at its definition:

function invokeInsertHook (vnode, queue, initial) {
  // delay insert hooks for component root nodes, invoke them after the
  // element is really inserted
  if (isTrue(initial) && isDef(vnode.parent)) {
    vnode.parent.data.pendingInsert = queue
  } else {
    for (let i = 0; i < queue.length; ++i) {
      queue[i].data.hook.insert(queue[i])
    }
  }
}
Copy the code

This function executes the INSERT hook, which, for components, is defined in createVNodeHooks, which are used to install the component hooks during the creation of child components as described in the previous article:

const componentVNodeHooks = {
  // ...
  insert (vnode: MountedComponentVNode) {
    const { context, componentInstance } = vnode
    if(! componentInstance._isMounted) { componentInstance._isMounted =true
      callHook(componentInstance, 'mounted')}// ...}},Copy the code

If the subcomponent does not have a custom mounted hook, call the subcomponent and add the mounted hook to it. Then, if the component root vNode is a component, init the patch repeatedly. In this process, the queue mentioned above is constantly adding hooks to it, and the vNode of the child component is always inserted in the queue first, so when the whole patch is finished, the hook function is called. The child’s INSERT hook is executed before the parent’s, meaning that the child’s mounted is executed first and the parent’s mounted is executed after.

BeforeMount the parent takes precedence over the child because the parent’s mountComponent takes precedence over the child’s execution. Then the child’s patch is executed. The initialization of the child is called, and the child’s mountComponent is called when it is initialized. So the father and the son.

BeforeUpdate and updated

A new Watcher is executed when the component executes mountComponent with a before function that calls the beforeUpdate hook:

export function mountComponent (vm: Component, el: ? Element, hydrating? : boolean) :Component {
  // ...
  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted) {
        callHook(vm, 'beforeUpdate')}}},true /* isRenderWatcher */)
  // ...
}

Copy the code

BeforeUpdate is called only after mounted. BeforeUpdate and updated are both executed after mounted.

The execution of the update is in flushSchedulerQueue function, it is defined in SRC/core/observer/scheduler. Js:

function flushSchedulerQueue () {
  // ...
  // Get the updatedQueue
  callUpdatedHooks(updatedQueue)
}

function callUpdatedHooks (queue) {
  let i = queue.length
  while (i--) {
    const watcher = queue[i]
    const vm = watcher.vm
    if (vm._watcher === watcher && vm._isMounted) {
      callHook(vm, 'updated')}}}Copy the code

FlushSchedulerQueue updatedQueue is the updated Watcher array, and then when we call callUpdatedHooks, The update hook is executed only if the current watcher is vm._watcher and the component is mounted. Where does vm._watcher === watcher come from?

The mounted component instantiates a render Watcher to listen for data changes on the VM:

export function mountComponent (vm: Component, el: ? Element, hydrating? : boolean) :Component {
  // ...
  // Here is the shorthand
  let updateComponent = () = > {
      vm._update(vm._render(), hydrating)
  }
  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted) {
        callHook(vm, 'beforeUpdate')}}},true /* isRenderWatcher */)
  // ...
}
Copy the code

And in the process of instantiating the rendering Watcher, will determine whether a isRenderWatcher, Watcher constructor as follows, defined in SRC/core/observer/Watcher. In js:

export default class Watcher {
  // ...
  constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object, isRenderWatcher? : boolean) {
    this.vm = vm
    if (isRenderWatcher) {
      vm._watcher = this
    }
    vm._watchers.push(this)
    // ...}}Copy the code

So vm. watcher is a render watcher, and if the current watcher is a render watcher, Assign the render Watcher to vm._watcher, and push it, the current watcher instance, to vm._watchers, _watcher === === === === === === === = This render watcher will execute its updated hook.

Vm._ismounted is defined in insert:

insert (vnode: MountedComponentVNode) {
  const { context, componentInstance } = vnode
  if(! componentInstance._isMounted) { componentInstance._isMounted =true
    callHook(componentInstance, 'mounted')}// ...
},
Copy the code

ComponentInstance._isMounted is false for first rendering, so only a mounted element is executed. When rerendering, _isMounted is true and updated. (Re-rendering is covered in the responsive Principles article)

BeforeDestroy and destroyed

These two hooks execute when $destroy:

Vue.prototype.$destroy = function () {
    const vm: Component = this
    if (vm._isBeingDestroyed) {
      return
    }
    callHook(vm, 'beforeDestroy')
    vm._isBeingDestroyed = true
    // remove self from parent
    const parent = vm.$parent
    if(parent && ! parent._isBeingDestroyed && ! vm.$options.abstract) { remove(parent.$children, vm) }// teardown watchers
    if (vm._watcher) {
      vm._watcher.teardown()
    }
    let i = vm._watchers.length
    while (i--) {
      vm._watchers[i].teardown()
    }
    // remove reference from data ob
    // frozen object may not have observer.
    if (vm._data.__ob__) {
      vm._data.__ob__.vmCount--
    }
    // call the last hook...
    vm._isDestroyed = true
    // invoke destroy hooks on current rendered tree
    vm.__patch__(vm._vnode, null)
    // fire destroyed hook
    callHook(vm, 'destroyed')
    // turn off all instance listeners.
    vm.$off()
    // remove __vue__ reference
    if (vm.$el) {
      vm.$el.__vue__ = null
    }
    // release circular reference (#6759)
    if (vm.$vnode) {
      vm.$vnode.parent = null}}Copy the code

BeforeDestroy this method is executed during component destruction, as seen in the preceding code. BeforeDestroy is executed and a set of destroyed tasks are done. A destroyed method is executed after the destruction. Vm. __Patch__ (vm._vnode, NULL). The second parameter is null because each component is a vUE instance and the child component is recursively destroyed when the current component executes $destroy.

BeforeDestroy is the parent of the beforeDestroy process, which means that the parent executes $destroy first and then destroys the child’s destroyed during the patch process. The patch process recursively prioritizes the execution of the innermost sub-components.