In Vue, hooks are used as events, which are called hookEvents in the Vue source code. It allows you to declare cycle hook functions through template declarative injection and add lifecycle handlers to third-party components.

    <Table @hook:updated="handleTableUpdated"></Table>
Copy the code

Start with a scenario

There is a complex table component from a third party, and the rendering time for data update of the table is 1s. As the rendering time is long, I hope to display a loading animation when the table is updated for better user experience.

A crude approach would be to modify the component’s source code directly, using beforeUpdate and updated to show loading, but this is a bad approach

  • This is a third-party component. The author probably compressed and constructed the code when releasing the component, which makes the code readability very low and it is difficult to modify the packaged code directly
  • You can fork a copy of the source code if you have it, but the author doesn’t have to release it
  • Instead of enjoying the open source community’s upgrade and maintenance of this component, you need to manually maintain it yourself

In short, modify the source code this solution is feasible, but not good, not elegant. Is there a way to declaratively inject a lifecycle function directly into a component’s template? In fact, you can, through hookEvent.

How do lifecycle functions work

Lifecycle functions are functions that are called at a particular point in time, so we need to focus on two things: 1, registration, and 2, invocation.

Let’s start with the invocation.

vm._self = vm
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

Above is a section of Vue source code, it can be seen that the lifecycle function is called by the callHook function, naturally, we go to see the callHook function code

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

So, callHook(‘created’) will execute $emit(‘hooks:created’) if vm._hasHookEvent is true, that is, an event with the special prefix hook: will be executed during the corresponding lifecycle. At this point we can make a bold argument – declaratively inject a lifecycle function from a Vue template using something like @hooks: Created, test it Works.

    <Table @hook:updated="handleTableUpdated"></Table>
Copy the code

hasHookEvent

The bold inference left out the condition that _hasHookEvent must be true, so let’s look at _hasHookEvent

const hookRE = /^hook:/ // start with hook:
Vue.prototype.$on = function (event: string | Array<string>, fn: Function) :Component {
  const vm: Component = this
  if (Array.isArray(event)) {
    for (let i = 0, l = event.length; i < l; i++) {
      vm.$on(event[i], fn)
    }
  } else {
    (vm._events[event] || (vm._events[event] = [])).push(fn)
    // optimize hook:event cost by using a boolean flag marked at registration
    // instead of a hash lookup
    if (hookRE.test(event)) {
      vm._hasHookEvent = true}}return vm
}
Copy the code

When the $on method is used to listen for events, if the event name begins with hooks: The vm._hasHookEvent is set to true when the event callback is registered. Since _hasHookEvent is true when the lifecycle function is called using callHook, So $emit(‘hooks: XXX ‘) and the registered lifecycle function will execute.

So, if you want to add lifecycle functions to a Vue component, there are three ways:

  • Add in the Vue option
  • Passed in the template@hooks:createdThis form
  • $on('hooks:created', cb) or $once('hooks:created', cb)