Vue插件實現(xiàn)過程中遇到的問題總結(jié)
場景介紹
最近做H5遇到了一個場景:每個頁面需要展示一個帶有標(biāo)題的頭部。一個實現(xiàn)思路是使用全局組件。假設(shè)我們創(chuàng)建一個名為TheHeader.vue的全局組件,偽代碼如下:
<template>
<h2>{{ title }}</h2>
</template>
<script>
export default {
props: {
title: {
type: String,
default: ''
}
}
}
</script>
創(chuàng)建好全局組件后,在每個頁面組件中引用該組件并傳入props中即可。例如我們在頁面A中引用該組件,頁面A對應(yīng)的組件是A.vue
<template>
<div>
<TheHeader :title="title" />
</div>
</template>
<script>
export default {
data() {
title: ''
},
created(){
this.title = '我的主頁'
}
}
</script>
使用起來非常簡單,不過有一點美中不足:如果頭部組件需要傳入的props很多,那么在頁面組件中維護對應(yīng)的props就會比較繁瑣。針對這種情況,有一個更好的思路來實現(xiàn)這個場景,就是使用Vue插件。
同樣是在A.vue組件調(diào)用頭部組件,使用Vue插件的調(diào)用方式會更加簡潔:
<template>
<div />
</template>
<script>
export default {
created(){
this.$setHeader('我的主頁')
}
}
</script>
我們看到,使用Vue插件來實現(xiàn),不需要在A.vue中顯式地放入TheHeader組件,也不需要在A.vue的data函數(shù)中放入對應(yīng)的props,只需要調(diào)用一個函數(shù)即可。那么,這個插件是怎么實現(xiàn)的呢?
插件實現(xiàn)
它的實現(xiàn)具體實現(xiàn)步驟如下:
- 創(chuàng)建一個SFC(single file component),這里就是TheHeader組件
- 創(chuàng)建一個plugin.js文件,引入SFC,通過Vue.extend方法擴展獲取一個新的Vue構(gòu)造函數(shù)并實例化。
- 實例化并通過函數(shù)調(diào)用更新Vue組件實例。
按照上面的步驟,我們來創(chuàng)建一個plugin.js文件:
import TheHeader from './TheHeader.vue'
import Vue from 'vue'
const headerPlugin = {
install(Vue) {
const vueInstance = new (Vue.extend(TheHeader))().$mount()
Vue.prototype.$setHeader = function(title) {
vueInstance.title = title
document.body.prepend(vueInstance.$el)
}
}
}
Vue.use(headerPlugin)
我們隨后在main.js中引入plugin.js,就完成了插件實現(xiàn)的全部邏輯過程。不過,盡管這個插件已經(jīng)實現(xiàn)了,但是有不少問題。
問題一、重復(fù)的頭部組件
如果我們在單頁面組件中使用,只要使用router.push方法之后,我們就會發(fā)現(xiàn)一個神奇的問題:在新的頁面出現(xiàn)了兩個頭部組件。如果我們再跳幾次,頭部組件的數(shù)量也會隨之增加。這是因為,我們在每個頁面都調(diào)用了這個方法,因此每個頁面都在文檔中放入了對應(yīng)DOM。
考慮到這點,我們需要對上面的組件進行優(yōu)化,我們把實例化的過程放到插件外面:
import TheHeader from './TheHeader.vue'
import Vue from 'vue'
const vueInstance = new (Vue.extend(TheHeader))().$mount()
const headerPlugin = {
install(Vue) {
Vue.prototype.$setHeader = function(title) {
vueInstance.title = title
document.body.prepend(vueInstance.$el)
}
}
}
Vue.use(headerPlugin)
這樣處理,雖然還是會重復(fù)在文檔中插入DOM。不過,由于是同一個vue實例,對應(yīng)的DOM沒有發(fā)生改變,所以插入的DOM始終只有一個。這樣,我們就解決了展示多個頭部組件的問題。為了不重復(fù)執(zhí)行插入DOM的操作,我們還可以做一個優(yōu)化:
import TheHeader from './TheHeader.vue'
import Vue from 'vue'
const vueInstance = new (Vue.extend(TheHeader))().$mount()
const hasPrepend = false
const headerPlugin = {
install(Vue) {
Vue.prototype.$setHeader = function(title) {
vueInstance.title = title
if (!hasPrepend) {
document.body.prepend(vueInstance.$el)
hasPrepend = true
}
}
}
}
Vue.use(headerPlugin)
增加一個變量來控制是否已經(jīng)插入了DOM,如果已經(jīng)插入了,就不再執(zhí)行插入的操作。優(yōu)化以后,這個插件的實現(xiàn)就差不多了。不過,個人在實現(xiàn)過程中有幾個問題,這里也一并記錄一下。
問題二、另一種實現(xiàn)思路
在實現(xiàn)過程中突發(fā)奇想,是不是可以直接修改TheHeader組件的data函數(shù)來實現(xiàn)這個組件呢?看下面的代碼:
import TheHeader from './TheHeader.vue'
import Vue from 'vue'
let el = null
const headerPlugin = {
install(Vue) {
Vue.prototype.$setHeader = function(title) {
TheHeader.data = function() {
title
}
const vueInstance = new (Vue.extend(TheHeader))().$mount()
el = vueInstance.$el
if (el) {
document.body.removeChild(el)
document.body.prepend(el)
}
}
}
}
Vue.use(headerPlugin)
看上去也沒什么問題。不過實踐后發(fā)現(xiàn),調(diào)用$setHeader方法,只有第一次傳入的值會生效。例如第一次傳入的是'我的主頁',第二次傳入的是'個人信息',那么頭部組件將始終展示我的主頁,而不會展示個人信息。原因是什么呢?
深入Vue源碼后發(fā)現(xiàn),在第一次調(diào)用new Vue以后,Header多了一個Ctor屬性,這個屬性緩存了Header組件對應(yīng)的構(gòu)造函數(shù)。后續(xù)調(diào)用new Vue(TheHeader)時,使用的構(gòu)造函數(shù)始終都是第一次緩存的,因此title的值也不會發(fā)生變化。Vue源碼對應(yīng)的代碼如下:
Vue.extend = function (extendOptions) {
extendOptions = extendOptions || {};
var Super = this;
var SuperId = Super.cid;
var cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {});
if (cachedCtors[SuperId]) { // 如果有緩存,直接返回緩存的構(gòu)造函數(shù)
return cachedCtors[SuperId]
}
var name = extendOptions.name || Super.options.name;
if (process.env.NODE_ENV !== 'production' && name) {
validateComponentName(name);
}
var Sub = function VueComponent (options) {
this._init(options);
};
Sub.prototype = Object.create(Super.prototype);
Sub.prototype.constructor = Sub;
Sub.cid = cid++;
Sub.options = mergeOptions(
Super.options,
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 Object.defineProperty calls for each instance created.
if (Sub.options.props) {
initProps$1(Sub);
}
if (Sub.options.computed) {
initComputed$1(Sub);
}
// allow further extension/mixin/plugin usage
Sub.extend = Super.extend;
Sub.mixin = Super.mixin;
Sub.use = Super.use;
// 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) {
Sub.options.components[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.
Sub.superOptions = Super.options;
Sub.extendOptions = extendOptions;
Sub.sealedOptions = extend({}, Sub.options);
// cache constructor
cachedCtors[SuperId] = Sub; // 這里就是緩存Ctor構(gòu)造函數(shù)的地方
return Sub
}
找到了原因,我們會發(fā)現(xiàn)這種方式也是可以的,我們只需要在plugin.js中加一行代碼
import TheHeader from './TheHeader.vue'
import Vue from 'vue'
let el = null
const headerPlugin = {
install(Vue) {
Vue.prototype.$setHeader = function(title) {
TheHeader.data = function() {
title
}
TheHeader.Ctor = {}
const vueInstance = new Vue(TheHeader).$mount()
el = vueInstance.$el
if (el) {
document.body.removeChild(el)
document.body.prepend(el)
}
}
}
}
Vue.use(headerPlugin)
每次執(zhí)行$setHeader方法時,我們都將緩存的構(gòu)造函數(shù)去掉即可。
問題三、是否可以不使用Vue.extend
實測其實不使用Vue.extend,直接使用Vue也是可行的,相關(guān)代碼如下:
import TheHeader from './TheHeader.vue'
import Vue from 'vue'
const vueInstance = new Vue(TheHeader).$mount()
const hasPrepend = false
const headerPlugin = {
install(Vue) {
Vue.prototype.$setHeader = function(title) {
vueInstance.title = title
if (!hasPrepend) {
document.body.prepend(vueInstance.$el)
hasPrepend = true
}
}
}
}
Vue.use(headerPlugin)
直接使用Vue來創(chuàng)建實例相較extend創(chuàng)建實例來說,不會在Header.vue中緩存Ctor屬性,相較來說是一個更好的辦法。但是之前有看過Vant實現(xiàn)Toast組件,基本上是使用Vue.extend方法而沒有直接使用Vue,這是為什么呢?
總結(jié)
到此這篇關(guān)于Vue插件實現(xiàn)過程中遇到問題的文章就介紹到這了,更多相關(guān)Vue插件實現(xiàn)問題內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Vue前端實現(xiàn)導(dǎo)出頁面為word的兩種方法
這篇文章主要介紹了Vue前端實現(xiàn)導(dǎo)出頁面為word的兩種方法,文中通過代碼示例和圖文介紹的非常詳細,對大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2024-12-12
Vue實現(xiàn)移除數(shù)組中特定元素的方法小結(jié)
這篇文章主要介紹了Vue如何優(yōu)雅地移除數(shù)組中的特定元素,文中介紹了單個去除和批量去除的操作方法,并通過代碼示例講解的非常詳細,具有一定的參考價值,需要的朋友可以參考下2024-03-03
vue3.0 CLI - 2.2 - 組件 home.vue 的初步改造
這篇文章主要介紹了vue3.0 CLI - 2.2 - 組件 home.vue 的初步改造,home.vue 組件有了兩個屬性:navs 和 tts 屬性,具體實例代碼大家跟隨小編一起通過本文學(xué)習(xí)吧2018-09-09
vue+element開發(fā)使用el-select不能回顯的處理方案
這篇文章主要介紹了vue+element開發(fā)使用el-select不能回顯的處理方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-07-07
vue如何使用js對圖片進行點擊標(biāo)注圓點并記錄它的坐標(biāo)
這篇文章主要介紹了vue如何使用js對圖片進行點擊標(biāo)注圓點并記錄它的坐標(biāo),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-04-04

