(9) Understand the principles of computed and Watch, and reduce the time to think about scenarios

A this._events private empty object property is mounted for each component instance during internal vUE initialization:

Vm._events = object.create (null) // No __proto__ attributeCopy the code

This contains the custom event collection on the current instance, the custom event center, which holds all the custom events of the current component. This.$on, this.$emit, this.$off, and this.$once all add, emit, and remove custom events from the event center, making up vue’s custom event system.

  • this.$on

Description: Listens for custom events on the current instance. Events can be emitted by vm.$emit, and the callback function receives any additional arguments that are passed to the event-firing function.

export default {
  created() {
    this.$on('test', res => {
      console.log(res)    
    })
  },
  methods: {
    handleClick() {
      this.$emit('test'.'hello-vue~')}}}Copy the code

The example above starts by adding a custom event named test to the event center of the current component instance. The second argument is the callback function for the custom event. When the handleClick method is triggered, it attempts to find the test custom event in the event center. Trigger it and pass the string to the callback hello-vue~ to print it out. Let’s look at the implementation of $on:

Vue.prototype.$on = function(event, fn) {const hookRE = /^hook:/ // check if custom event names start with hook: const vm = thisif(array.isarray (event)) {// If the first argument is an Arrayfor (let i = 0; i < event.length; i++) {
      this.$on(event[I], fn) // recursive}}else{(vm) _events [event] | | (vm) _events [event] = [])), push (fn) / / if there is a corresponding event name is push, not created as an empty array and then pushif(hookre.test (event)) {// If hook: start vm._hasHookEvent =true// The flag bit istrue}}return vm
}
Copy the code

This is the implementation of $on, which takes two parameters, the custom event name and the corresponding callback function fn. The main method is to mount the corresponding event name key under the event center _events. The key corresponding to the event name is an array form, so that the callback of the same event name will be in an array. The following _hasHookEvent flag bit indicates whether to listen for the component’s hook function, as illustrated later in the example.

  • this.$emit

Description: Triggers an event on the current instance, with additional parameters passed to the listener callback.

Vue.prototype.$emit = function (event) {
  const vm = this
  letCBS = vm._events[event] // Find the callback set corresponding to the event nameif(CBS) {const args = toArray(arguments, 1) // Convert additional arguments to arraysfor (leti = 0; i < cbs.length; I ++) {CBS [I]. Apply (vm, args) // execute the corresponding callback set one by one}}return vm
}
Copy the code

The implementation of $emit is better understood by finding the callback set corresponding to the event in the event center, converting the remaining parameters of $emit into an ARGS array, and finally executing the callbacks in the callback set one by one and passing them to arGS. This pair of unpretentious apis helps us understand three small things:

1. Understand the principle of custom events

app.vue
<template>
  <child-component @test='handleTest' />
</template>
export default {
  methods: {
    handleTest(res) {
      console.log(res)
    }
  }
}

----------------------------------------

child.vue
<template>
  <button @click='onClick'>btn</button>
</template>
export default {
  methods: {
    onClick() {
      this.$emit('test'.'hello-vue~')}}}Copy the code

This is a familiar example of parent and child components communicating via custom events. After compiling the template, the parent component will add the custom event test and its callback handleTest to the event center of the child component through $ON. When the child component fires the custom event test through $emit, the parent component will add the custom event test and its callback handleTest to the event center of the child component. It looks for test in its event center, passes hello-vue~ to the callback and executes it, but since the callback handleTest is defined in the parent’s scope, it looks like the parent component communicates with the parent component.

2. Listen for the component’s hook function

$on = $on; $on = $on;

app.vue
<template>
  <child-component @hook:created='handleHookEvent' />
</template>

Copy the code

The above example fires the handleHookEvent callback defined in the parent component when the child component’s Created hook fires. Let’s take a look at an example from the official website of how using this feature can help us write more elegant code:

Before listening on component hooks:mountedPicker = new Pikaday({// Pikaday is a date selection library field: this.$refs.input,
    format: 'YYYY-MM-DD'})},beforeDestroy() {// destroy the date picker this.picker.destroy()} after listening for the component hook:mounted() {
  this.attachDatepicker('startDateInput')
  this.attachDatepicker('endDateInput'// Add date options for both inputs}, methods: {attachDatepicker(refName) {// Encapsulate as a method const picker = new Pikaday({// Pikaday is a date selection library field: this.$refs[refName], // Add date format for input:'YYYY-MM-DD'
    })

    this.$once('hook:beforeDestroy', () => {// Listen to beforeDestroy hook picker.destroy() // Destroy date picker}) //$onceand$onSimilar, but only fired once}}Copy the code

First, you don’t have to mount an additional attribute under the current instance, and second, you can encapsulate it as a method, making it easier to reuse.

3. Don’t usevuexCross component communication

When redeveloping a component library, it is not practical to introduce vuEX as a strong dependency because they are all independent components, and many times slots are used to place sub-components, so the location, nesting, and number of sub-components are not determined, so it is important to communicate across components within the component library.

The child component uses the name attribute of the parent component to find the corresponding instance, and then uses $emit to trigger the parent component’s custom event. The parent component has already added the custom event using $ON.

exportDefault {methods: {// Mixins using dispatch(componentName, eventName, Params) {let parent = this.$parent || this.$root// Find the parent componentlet name = parent.$options.name // The name property of the parent componentwhile(parent && (! name || name ! == componentName)) {parent = parent$parent// Go all the way upif (parent) {
          name = parent.$options.name // reassign name}}if(parent) {// Find the matching component instance parent.$emit.apply(parent, [eventName].concat(params))  // $emitTrigger a custom event}}}}Copy the code

Here are the use cases within the form validation component:

iForm
iFormItem

IForm component: <template> <div> <slot /> </div> // only one slot </template> <script>export default {
  name: "iForm"// The component name is importantdata() {
    return{fields: [] // Collect a collection of all form items}; },created() {
    this.$on("on-form-item-add", field => {  // $onMust be better than$emitThis.fields.push (field) // add to collection}); }, methods: {validataAll() {// Validate all interface methods this.fields.foreach (item => {item.validateval () // execute the validateVal method in each form item}); }}}; </script>Copy the code

The template has only one slot slot. This component does two things. It collects all instances of form items into fields, provides a validataAll method that validates all form items, and then looks at the iFormItem component:

<template>
  <div>
    <input v-model="curValue" style="border: 1px solid #aaa;" />
    <span style="color: red;" v-show="showTip"</span> </div> </template> <script> import emitter from"./emitter"// Introduce the previous dispatch methodexport default {
  name: "iFormItem", mixins: [emitter], //data() {
    return {
      curValue: ""// Form item value showTip:false}; },created() {
    this.dispatch("iForm"."on-form-item-add"// Pass the current instance to the iForm component}, methods: {validateVal() {// The validation method for a form itemif (this.curValue === "") {// cannot be null this.showtip =true// Verification failed}}}}; </script>Copy the code

Here we know that the original form validation principle is to pass an instance of each form item to iForm, and then execute the validation method for each form item within iForm, so that all form items can be verified at once. Form validation calls:

<template>
  <div>
    <i-form ref='form'< I > / / reference - form - item / > < - form - item/I > < I - form - item / > < - form - item/I > < I - form - item / > < / I - form > < button @ click ="submit"</button> </div> </template> <script> import iForm from"./form"
import iFormItem from "./form-item"

export default {
  methods: {
    submit() {
      this.$refs['form'}}, Components: {iForm, iFormItem}}; </script>Copy the code

The $ON and $emit apis are used to find component instances by name, regardless of nesting or number, and then use the event API to pass parameters across components.

Note: when $ON and $emit are used together, $ON takes precedence over $emit. The event must be added to the event center of the instance before it can be fired.

  • this.$off

Description: Removes custom event listeners in three forms, depending on the parameters passed in:

  • If no arguments are provided, all event listeners are removed;
  • If only events are provided, remove all listeners for that event;
  • If both an event and a callback are provided, only the listener for that callback is removed.
export default {
  created() {
    this.$on('test1', this.test1)
    this.$on('test2', this.test2)
  },
  mounted() {
    this.$off() / / no parameters, empty event center}} -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --export default {
  created() {
    this.$on('test1', this.test1)
    this.$on('test2', this.test2)
  },
  mounted() {
    this.$off('test1') // Remove it from the event centertest1}} -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --export default {
  created() {
    this.$on('test1', this.test1)
    this.$on('test1', this.test3)
    this.$on('test2', this.test2)
  },
  mounted() {
    this.$off('test1', this.test3) // Remove the event from the event centertest1,test3 Callback}}Copy the code

Now that we know how the API is called, let’s look at how $off is implemented:

Vue.prototype.$off = function (event, fn) {
  const vm = this
  if(! Arguments.length) {// If the vm._events = object.create (null) // resets the event centerreturn vm
  }
  
  if(array.isarray (event)) {// event if it is an Arrayfor (let i = 0, l = event.length; i < l; i++) {
      vm.$off(event[I], fn) // Recursive empty}return vm
  }
  
  if(! Vm. _events[event] = null // Clear all corresponding callbacksreturnVm} const CBS = vm._events[event] // Get the callback setlet cb
  let i = cbs.length
  while(I --) {cb = CBS [I] // each item in the callback setif(cb = = = fn | | cb, fn = = = fn) {/ / cb, fn$onceWhen mounted cbs.splice(I, 1) // find the corresponding callback and remove it from the collectionbreak}}return vm
}
Copy the code

Also divided into three cases, according to the different parameters to do respectively.

  • this.$once

Description: Listens for a custom event, but fires only once. Removes the listener after the first time.

The effect is similar to $on, except that it is removed from the event center after firing once. So it is easy to understand how to implement it. First implement the function through $on, and remove the event from the event center when it is triggered. Here’s how it works:

Vue.prototype.$once = function (event, fn) {
  const vm = this
  function on () {
    vm.$off(event, on) fn. Apply (VM, arguments)} on.fn = fn //$on(event, ON) // Add ON to the event centerreturn vm
}
Copy the code

First, mount the fn callback to the on function and register the ON function with the event center. When the custom event is emitted, the on function will be executed in $emit. Then, the on function will be removed by $off. At this time, the event center does not have on function, the callback function is also executed once, complete the $once function ~

$on adds events to the event center; $emit is the event that triggers the event center; $off removes the event from the event center; $once triggers an event in the event center. Even such obscure apis, with an understanding of how they are implemented, can be used in more scenarios

We will end this chapter with a typical interview question that Vue will be asked.

The interviewer smiled politely and asked,

  • Talk about the mechanics of customizing events.

Dui back:

  • Use of child componentsthis.$emitWhen an event is fired, it looks for the corresponding event in the event center of the current instance and executes it. However, the event callback is defined in the scope of the parent component, so$emitThe arguments in the parent component are passed to the parent component’s callback function, thus completing parent-child component communication.

Understand the extend and $mount principle and implement an imperative Confirm popover component

Easy to click a like or follow bai, also easy to find ~

Reference:

Vue. Js source code comprehensive in-depth analysis

Vue.js is easy to understand

Share a component library for everyone, may use up ~ ↓

A library of vUE functional components that you might want to use.