SoFunction
Updated on 2025-04-05

Summary of problems encountered during the implementation of Vue plug-in

Scene introduction

Recently, I encountered a scenario when doing H5: Each page needs to display a header with a title. One implementation idea is to use global components. Suppose we create a global component named, the pseudo-code is as follows:

<template>
    <h2>{{ title }}</h2>
</template>

<script>
export default {
props: {
    title: {
        type: String,
        default: ''
    }
}
}
</script>

After creating the global component, refer to the component in each page component and pass it into props. For example, we refer to this component in page A, and the corresponding component of page A is

&lt;template&gt;
    &lt;div&gt;
        &lt;TheHeader :title="title" /&gt;
    &lt;/div&gt;
&lt;/template&gt;
&lt;script&gt;
    export default {
        data() {
            title: ''
        },
        created(){
             = 'My Homepage'
        }
    }
&lt;/script&gt;

It is very simple to use, but there is one disadvantage: if the head component needs a lot of props passed in, it will be more cumbersome to maintain the corresponding props in the page component. In this case, there is a better idea to implement this scenario, which is to use the Vue plugin.

Also, when calling the header component in the component, the calling method using the Vue plugin will be more concise:

&lt;template&gt;
    &lt;div /&gt;
&lt;/template&gt;
&lt;script&gt;
    export default {
        created(){
            this.$setHeader('My Homepage')
        }
    }
&lt;/script&gt;

We see that using the Vue plugin to implement it, there is no need to explicitly put the TheHeader component in it, nor does it need to put the corresponding props in the data function, it only needs to call one function. So, how is this plugin implemented?

Plugin implementation

Its implementation steps are as follows:

  1. Create an SFC (single file component), here is the TheHeader component
  2. Create a file, introduce SFC, and use method extension to get a new Vue constructor and instantiate it.
  3. Instantiate and update the Vue component instance through function calls.

Follow the above steps and we will create a file:

import TheHeader from './'
import Vue from 'vue'

const headerPlugin = {
    install(Vue) {
        const vueInstance = new ((TheHeader))().$mount()
        .$setHeader = function(title) {
             = title
            (vueInstance.$el)
            
        }
    }
}
(headerPlugin)

We then introduced it in, completing the entire logical process of plug-in implementation. However, although this plugin has been implemented, there are many problems.

Question 1: Repeated head components

If we use it in a single page component, as long as we use the method, we will find a magical problem: two head components appear on the new page. If we jump a few more times, the number of head components will also increase. This is because we call this method on each page, so each page has the corresponding DOM in the document.

With this in mind, we need to optimize the above components, and we put the instantiation process outside the plug-in:

import TheHeader from './'
import Vue from 'vue'

const vueInstance = new ((TheHeader))().$mount()
const headerPlugin = {
    install(Vue) {
        .$setHeader = function(title) {
             = title
            (vueInstance.$el)
            
        }
    }
}
(headerPlugin)

This way, although the DOM will be inserted repeatedly into the document. However, since it is the same vue instance, the corresponding DOM has not changed, so there is always only one DOM inserted. In this way, we solve the problem of displaying multiple head components. In order not to repeatedly perform the operation of inserting the DOM, we can also do an optimization:

import TheHeader from './'
import Vue from 'vue'

const vueInstance = new ((TheHeader))().$mount()
const hasPrepend = false
const headerPlugin = {
    install(Vue) {
        .$setHeader = function(title) {
             = title
            if (!hasPrepend) {
                (vueInstance.$el)
                hasPrepend = true
            }
            
        }
    }
}
(headerPlugin)

Add a variable to control whether the DOM has been inserted. If it has been inserted, the insertion operation will no longer be performed. After optimization, the implementation of this plug-in will be almost done. However, I have several problems in the implementation process, so I will also record them here.

Question 2, Another way of implementation

During the implementation process, can we directly modify the data function of TheHeader component to implement this component? Look at the following code:

import TheHeader from './'
import Vue from 'vue'

let el = null
const headerPlugin = {
    install(Vue) {
        .$setHeader = function(title) {
             = function() {
                title
            }
            const vueInstance = new ((TheHeader))().$mount()
            el = vueInstance.$el
            if (el) {
                (el)
                (el)
            }
            
        }
    }
}
(headerPlugin)

It looks like nothing wrong. However, after practice, it was found that when calling the $setHeader method, only the first value passed in will take effect. For example, the first time it is passed in is 'My homepage' and the second time it is 'Personal Information', then the head component will always display my homepage without showing personal information. What is the reason?

After digging into the Vue source code, I found that after the first call to new Vue, the Header has an additional Ctor attribute, which caches the constructor corresponding to the Header component. When new Vue(TheHeader) is called later, the constructor used is always cached for the first time, so the value of the title will not change. The corresponding code of Vue source code is as follows:

 = function (extendOptions) {
    extendOptions = extendOptions || {};
    var Super = this;
    var SuperId = ;
    var cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {}); 
    if (cachedCtors[SuperId]) { // If there is a cache, return the cached constructor directly      return cachedCtors[SuperId]
    }

    var name =  || ;
    if (.NODE_ENV !== 'production' &amp;&amp; name) {
      validateComponentName(name);
    }

    var Sub = function VueComponent (options) {
      this._init(options);
    };
     = ();
     = Sub;
     = cid++;
     = mergeOptions(
      ,
      extendOptions
    );
    Sub['super'] = Super;

    // For props and computed properties, we define the proxy getters on
    // the Vue instances at extension time, on the extended prototype. This
    // avoids  calls for each instance created.
    if () {
      initProps$1(Sub);
    }
    if () {
      initComputed$1(Sub);
    }

    // allow further extension/mixin/plugin usage
     = ;
     = ;
     = ;

    // create asset registers, so extended classes
    // can have their private assets too.
    ASSET_TYPES.forEach(function (type) {
      Sub[type] = Super[type];
    });
    // enable recursive self-lookup
    if (name) {
      [name] = Sub;
    }

    // keep a reference to the super options at extension time.
    // later at instantiation we can check if Super's options have
    // been updated.
     = ;
     = extendOptions;
     = extend({}, );

    // cache constructor
    cachedCtors[SuperId] = Sub; // This is where the Ctor constructor is cached    return Sub
  }

After finding the reason, we will find that this method is also OK. We only need to add a line of code to it.

import TheHeader from './'
import Vue from 'vue'

let el = null
const headerPlugin = {
    install(Vue) {
        .$setHeader = function(title) {
             = function() {
                title
            }
             = {}
            const vueInstance = new Vue(TheHeader).$mount()
            el = vueInstance.$el
            if (el) {
                (el)
                (el)
            }
            
        }
    }
}
(headerPlugin)

Each time we execute the $setHeader method, we just remove the cached constructor.

Question 3: Can I not use it

In fact, it is not used. It is feasible to use Vue directly. The relevant code is as follows:

import TheHeader from './'
import Vue from 'vue'

const vueInstance = new Vue(TheHeader).$mount()
const hasPrepend = false
const headerPlugin = {
    install(Vue) {
        .$setHeader = function(title) {
             = title
            if (!hasPrepend) {
                (vueInstance.$el)
                hasPrepend = true
            }
            
        }
    }
}
(headerPlugin)

Using Vue to create instances directly is a better way to create instances than extend, the Ctor attributes will not be cached in it. However, I have seen Vant implementing Toast components before, and basically use methods instead of using Vue directly. Why is this?

Summarize

This is the article about problems encountered in the implementation of Vue plug-in. For more related Vue plug-in problems, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!