Componentization – Life cycle

Each Vue instance goes through a series of initialization procedures before being created. For example, you need to set up data listening, compile templates, mount instances to the DOM, update the DOM when data changes, and so on. There are also functions called lifecycle hooks that run along the way, giving users the opportunity to add their own code to specific scenarios. – Official website: Instance lifecycle hooks

For those of us who use Vue, we have to say hello to the lifecycle every day and do different things in each lifecycle. For example, I like to call interfaces in the Created phase and DOM in the Mounted phase. This article will look at how the Vue lifecycle hook functions are implemented.

All life cycle method is called callHook, it defined in SRC/core/instance/lifecycle:

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

CallHook does several things:

  1. Based on the string passed inhookGets the corresponding array of callback functions
  2. Loop through the array of callback functions, and then execute, while executing thevmFunction execution context

In the componentized merge configuration section, we explained that during the merge options process, the life cycle of each stage is also merged as an array into vm.$options, so that callHook can be executed completely.

Now that you know how to execute/trigger, take a look at the call timing for each lifecycle.

beforeCreate & created

BeforeCreate & created is called during the initialization phase of the instance Vue, which is defined in _init:

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

You can see that beforeCreate and Created are called before and after initState, respectively. InitState is used to initialize Props, Methods, Data, Computed, Watch, etc., so we can’t get the value of this. XXX in beforeCreate. Also, the DOM has not been rendered at the time the hooks are executed, so you cannot manipulate the DOM.

beforeMount & mounted

BeforeMount hooks on the mount is called during execution, which is mounted before the DOM, it is the call time in mountComponent function, defined in SRC/core/instance/lifecycle. In js:

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

BeforeMount is called before updateComponent is executed. Mounted is executed after updateComponent is executed to patch VNode to the real DOM. / / Mounted: / / new Vue(options) / / mounted: / / new Vue(options)

The invokeInsertHook function invokeInsertHook is invoked when a component’s VNode patch is inserted into the DOM. InsertedVnodeQueue (SRC /core/vdom/patch.js) insertedVnodeQueue (SRC /core/vdom/patch.js)

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

The insert hook function is defined by SRC /core/vdom/create-component.js componentVNodeHooks:

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

InsertedVnodeQueue inserts children before parents, so mounted is executed in the same order as synchronized children.

beforeUpdate & updated

BeforeUpdate & updated is triggered when data is updated. This involves bidirectional data binding, updating, and more on this later.

BeforeUpdate execution timing is in the before function rendering Watcher:

export function mountComponent(vm: Component, el: ? Element, hydrating? : boolean) :Component {
  // ...

  // 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 */
  );
  // ...
}
Copy the code

This generates a render Watcher that fires beforeUpdate every time it is updated.

The execution of the updated timing is when flushSchedulerQueue function calls, defined in SRC/core/observer/scheduler. In 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

We’re going to take a look at flushSchedulerQueue, and we’ll get into that later. UpdatedQueue is an updated Watcher array, which is iterated by callUpdatedHooks. Updated only if vm._watcher === watcher && vm._isMounted.

We mentioned earlier that a render Watcher is instantiated in the mount procedure (calling mountComponent) :

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

If isRenderWatcher = true, the current Watcher is assigned to vm._watcher:

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

Vm. _watcher is used to listen for data changes on the VM and then re-render, so it is a render dependent Watcher. So in the callUpdatedHooks function, the updated hook function is executed only after the vm._watcher callback completes.

beforeDestroy & destroyed

BeforeDestroy & destroyed is destroyed in the component phase trigger, component will eventually call $destroy methods, defined in SRC/core/instance/lifecycle. In js:

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

Call beforeDestroy first, then remove itself from parent, remove watcher, remove observer reference, execute vm. __Patch__ (vm._vnode, null) to trigger the child component’s destroy hook function, also recursive call, 8. Like Mounted, the son comes before the father.

activated & deactivated

Activated & deactivated are KeepAlive’s exclusive lifecycle hooks.

conclusion

This section describes when the lifecycle hooks are triggered, which allows access to props, data, method, etc. DOM operations can be performed in mounted. Knowing that timers can be cleared during the Destroy phase, DOM references can be assigned to null values, and so on gives you a lot of flexibility when calling methods and operations in different life cycles.

Source code analysis GitHub address

Reference: Vue. Js technology revealed