Vue principle analysis (ten) : understand the event API principle and in the component library

There is a beautiful confirmation popup component in the course of developing enterprise-class mobile end music Web App of Lao huang’s Vue2.0, as follows:

ref

this.$Confirm({... }) .then(confirm => { ... }) .catch(cancel => { ... })Copy the code

Extend and $mount

Both of these apis are provided by VUE, but are not used much in normal business development. This pair of apis is also used internally in VUE. When a nested component is encountered, when a subcomponent is first converted to a VNode in component form, the imported component object is converted to a subcomponent constructor using EXTEND as a VNode property Ctor. This constructor is then instantiated when the VNode is converted into a real Dom; Finally, after the completion of the instantiation, manually call $mount to mount, insert the real Dom into the parent node to complete the rendering.

So the popover component can be implemented by extending the component object ourselves to the constructor, then manually calling $mount to the real Dom, where we specify a parent node to insert into the specified location.

Before we start, let’s take a little more time to understand the details of the process:

extend

Extends inherits properties, prototype methods, static methods, and so on from the base class constructor, and returns Sub, a constructed child component constructor. Has the same capabilities as the vUE base class, and executes the inherited _init method on instantiation to initialize the child component.

Vue.extend = function(extendOptions = {}) {const Super = this / / Vue base class constructor const name = extendOptions. Name | | Super. Options. The name const Sub =function} sub.prototype = object.create (super.prototype) // Vue inherited base class initializes the definition method of prototype Sub. Prototype. The constructor = Sub / / Sub constructor to subclass options = mergeOptions (/ / subclass merge options. Super options, // Components, directives, filters, _base extendOptions // incoming component object) Sub['super'] = Super // Vue base class // Assign static methods of base class to subclasses sub.extend = super.extend sub.mixin = super.mixin sub.use = super.use ASSET_TYPES.forEach(function (type{/ / /'component'.'directive'.'filter']
    Sub[type] = Super[type]})if(name) {let the component recursively call itself, So be sure to define the name attribute Sub.options.components[name] = Sub // mount the subclass to its components attribute} sub.superoptions = super.options Sub.extendOptions = extendOptionsreturnSub // Returns the constructor of the child component}Copy the code

Instantiate the Sub

Performs a series of operations for initializing the _init component, initialization events, life cycles, states, and so on. Mount variables defined in data or props to the current this instance, and return an instantiated object.

Vue.prototype._init = function(options) {// Initialize... initLifecycle(vm) initEvents(vm) initRender(vm) callHook(vm,'beforeCreate')
  initInjections(vm)
  initState(vm)
  initProvide(vm)
  callHook(vm, 'created'// The initialization phase is complete...if (vm.$options.el) {// Start the mount phase VM.$mount(vm.$options.el) // Perform mount}}Copy the code

$mount

After you get the initialized object, mount the component. First turn the current render function into a VNode, then turn the VNode into a real Dom and insert it into the page to complete the rendering. After the mount is complete, the $EL property will be mounted under the current component instance this, which is the actual Dom after the mount, and we will need to use this property.

Component transformation

1. Write the component (complete code at the end)

Since this is called as a Promise, the Promise object is returned after display, and only the main JavaScript part is released here:

export default {
  data() {
    return {
      showFlag: false,
      title: "Are you sure you cleared all history?", // You can use props ConfirmBtnText:"Sure"CancelBtnText: cancelBtnText:"Cancel"}; }, methods: {show(cb) {// add a callback before implementing the Promise this.showflag =true;
      typeof cb === "function" && cb.call(this, this);
      returnNew Promise((resolve, reject) => {// return Promise this. Reject = reject; // Use this. Resolve = resolve; }); },cancel() {
      this.reject("cancel"); // Throw a string this.hide(); },confirm() {
      this.resolve("confirm");
      this.hide();
    },
    hide() {
      this.showFlag = false;
      document.body.removeChild(this.$el); // End remove Dom this.$destroy(a); // Perform component destruction}}};Copy the code

2. Change the invocation method

The component object is already there; the next step is to make it imperative callable:

confirm/index.js

import Vue from 'vue';
import Confirm from './confirm'; // Import componentsletnewInstance; const ConfirmInstance = Vue.extend(Confirm); // Create constructor const initInstance = () => {// Execute method to mount newInstance = new ConfirmInstance(); / / instantiate the document. The body. The appendChild (newInstance.$mount().$el); // mount it manually after instantiation$elReal Dom, add it to the end of body}exportDefault options => {Export a method that accepts configuration parametersif(! newInstance) { initInstance(); // Mount} object. assign(newInstance, options); // newInstance is an object after instantiation, so the data in data will be mounted to this, passing an object to merge with itreturnNewInstance. Show (vm => {// display pop-up newInstance = null; // Empty the instance object})}Copy the code

You can actually use Install to make a plug-in here, but I’ve skipped it before. The component object is first converted into a component constructor using extend, and the real Dom is mounted at the end of the body by executing the initInstance method. Why didn’t we use props instead of data? Because when they are initialized, they will be mounted under this, but data code is very small. Export a method for external use, accept configuration parameters, and return a Promise object when called.

3. Mount it globally

Mount the exported method to the Vue prototype in main.js to make it a global method:

import Confirm from './base/confirm/index';

Vue.prototype.$Confirm= Confirm; Try calling ~ this.$Confirm({
  title: 'Vue great! '
}).then(confirm => {
  console.log(confirm)  
}).catch(cancel => {
  console.log(cancel)
})
Copy the code

The complete code of the component is as follows:

confirm/confirm.vue

<template>
  <transition name="confirm-fade">
    <div class="confirm" v-show="showFlag">
      <div class="confirm-wrapper">
        <div class="confirm-content">
          <p class="text">{{title}}</p>
          <div class="operate" @click.stop>
            <div class="operate-btn left" @click="cancel">{{cancelBtnText}}</div>
            <div class="operate-btn" @click="confirm">{{ConfirmBtnText}}</div>
          </div>
        </div>
      </div>
    </div>
  </transition>
</template>

<script>
export default {
  data() {
    return {
      showFlag: false,
      title: "Are you sure you cleared all history?", 
      ConfirmBtnText: "Sure",
      cancelBtnText: "Cancel"
    };
  },
  methods: {
    show(cb) {
      this.showFlag = true;
      typeof cb === "function" && cb.call(this, this);
      return new Promise((resolve, reject) => {
        this.reject = reject;
        this.resolve = resolve;
      });
    },
    cancel() {
      this.reject("cancel");
      this.hide();
    },
    confirm() {
      this.resolve("confirm");
      this.hide();
    },
    hide() {
      this.showFlag = false;
      document.body.removeChild(this.$el);
      this.$destroy(a); }}}; </script> <style scoped lang="stylus"> .confirm { position: fixed; left: 0; right: 0; top: 0; bottom: 0; z-index: 998; Background: rgba(0, 0, 0, 0.3); &. Confirm-fade-enter-active {animation: confirm-fadein 0.3s; . Confirm-content {animation: confirm-zoom 0.3s; } } .confirm-wrapper { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 999; .confirm-content { width: 270px; border-radius: 13px; background:# 333;.text { padding: 19px 15px; line-height: 22px; text-align: center; font-size: 18px; Color: rgba(255, 255, 255, 0.5); } .operate { display: flex; align-items: center; text-align: center; font-size: 18px; .operate-btn { flex: 1; line-height: 22px; padding: 10px 0; Border-top: 1px solid rgba(0, 0, 0, 0.3); Color: rgba(255, 255, 255, 0.3); &.left {border-right: 1px solid rgba(0, 0, 0, 0.3); } } } } } } @keyframes confirm-fadein { 0% {opacity: 0; } 100% {opacity: 1; } } @keyframes confirm-zoom { 0% {transform: scale(0); 50%} {the transform: scale (1.1); } 100% {transform: scale(1); } } </style>Copy the code

Try to implement a global alert component, the principle is similar ~

We end this chapter with the usual interview question that Vue will be asked

The interviewer smiled politely and asked,

  • Please explain the principle of the imperative popover component in the component library.

Dui back:

  • useextendTurn the component into a constructor, and after instantiating this constructor, you get$elProperties, that is, the reality of the componentDomAt this time we can operate to get the realDomTo mount arbitrarily, use imperative also can be called.

The Transition component doesn’t let transitions/animations fall short

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 written by the author, who knows which day to use the ~ ↓

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