Vue插件實(shí)現(xiàn)過(guò)程中遇到的問(wèn)題總結(jié)
場(chǎng)景介紹
最近做H5遇到了一個(gè)場(chǎng)景:每個(gè)頁(yè)面需要展示一個(gè)帶有標(biāo)題的頭部。一個(gè)實(shí)現(xiàn)思路是使用全局組件。假設(shè)我們創(chuàng)建一個(gè)名為TheHeader.vue的全局組件,偽代碼如下:
<template> <h2>{{ title }}</h2> </template> <script> export default { props: { title: { type: String, default: '' } } } </script>
創(chuàng)建好全局組件后,在每個(gè)頁(yè)面組件中引用該組件并傳入props中即可。例如我們?cè)陧?yè)面A中引用該組件,頁(yè)面A對(duì)應(yīng)的組件是A.vue
<template> <div> <TheHeader :title="title" /> </div> </template> <script> export default { data() { title: '' }, created(){ this.title = '我的主頁(yè)' } } </script>
使用起來(lái)非常簡(jiǎn)單,不過(guò)有一點(diǎn)美中不足:如果頭部組件需要傳入的props很多,那么在頁(yè)面組件中維護(hù)對(duì)應(yīng)的props就會(huì)比較繁瑣。針對(duì)這種情況,有一個(gè)更好的思路來(lái)實(shí)現(xiàn)這個(gè)場(chǎng)景,就是使用Vue插件。
同樣是在A.vue組件調(diào)用頭部組件,使用Vue插件的調(diào)用方式會(huì)更加簡(jiǎn)潔:
<template> <div /> </template> <script> export default { created(){ this.$setHeader('我的主頁(yè)') } } </script>
我們看到,使用Vue插件來(lái)實(shí)現(xiàn),不需要在A.vue中顯式地放入TheHeader組件,也不需要在A.vue的data函數(shù)中放入對(duì)應(yīng)的props,只需要調(diào)用一個(gè)函數(shù)即可。那么,這個(gè)插件是怎么實(shí)現(xiàn)的呢?
插件實(shí)現(xiàn)
它的實(shí)現(xiàn)具體實(shí)現(xiàn)步驟如下:
- 創(chuàng)建一個(gè)SFC(single file component),這里就是TheHeader組件
- 創(chuàng)建一個(gè)plugin.js文件,引入SFC,通過(guò)Vue.extend方法擴(kuò)展獲取一個(gè)新的Vue構(gòu)造函數(shù)并實(shí)例化。
- 實(shí)例化并通過(guò)函數(shù)調(diào)用更新Vue組件實(shí)例。
按照上面的步驟,我們來(lái)創(chuàng)建一個(gè)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,就完成了插件實(shí)現(xiàn)的全部邏輯過(guò)程。不過(guò),盡管這個(gè)插件已經(jīng)實(shí)現(xiàn)了,但是有不少問(wèn)題。
問(wèn)題一、重復(fù)的頭部組件
如果我們?cè)趩雾?yè)面組件中使用,只要使用router.push方法之后,我們就會(huì)發(fā)現(xiàn)一個(gè)神奇的問(wèn)題:在新的頁(yè)面出現(xiàn)了兩個(gè)頭部組件。如果我們?cè)偬鴰状?,頭部組件的數(shù)量也會(huì)隨之增加。這是因?yàn)椋覀冊(cè)诿總€(gè)頁(yè)面都調(diào)用了這個(gè)方法,因此每個(gè)頁(yè)面都在文檔中放入了對(duì)應(yīng)DOM。
考慮到這點(diǎn),我們需要對(duì)上面的組件進(jìn)行優(yōu)化,我們把實(shí)例化的過(guò)程放到插件外面:
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)
這樣處理,雖然還是會(huì)重復(fù)在文檔中插入DOM。不過(guò),由于是同一個(gè)vue實(shí)例,對(duì)應(yīng)的DOM沒(méi)有發(fā)生改變,所以插入的DOM始終只有一個(gè)。這樣,我們就解決了展示多個(gè)頭部組件的問(wèn)題。為了不重復(fù)執(zhí)行插入DOM的操作,我們還可以做一個(gè)優(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)
增加一個(gè)變量來(lái)控制是否已經(jīng)插入了DOM,如果已經(jīng)插入了,就不再執(zhí)行插入的操作。優(yōu)化以后,這個(gè)插件的實(shí)現(xiàn)就差不多了。不過(guò),個(gè)人在實(shí)現(xiàn)過(guò)程中有幾個(gè)問(wèn)題,這里也一并記錄一下。
問(wèn)題二、另一種實(shí)現(xiàn)思路
在實(shí)現(xiàn)過(guò)程中突發(fā)奇想,是不是可以直接修改TheHeader組件的data函數(shù)來(lái)實(shí)現(xiàn)這個(gè)組件呢?看下面的代碼:
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)
看上去也沒(méi)什么問(wèn)題。不過(guò)實(shí)踐后發(fā)現(xiàn),調(diào)用$setHeader方法,只有第一次傳入的值會(huì)生效。例如第一次傳入的是'我的主頁(yè)',第二次傳入的是'個(gè)人信息',那么頭部組件將始終展示我的主頁(yè),而不會(huì)展示個(gè)人信息。原因是什么呢?
深入Vue源碼后發(fā)現(xiàn),在第一次調(diào)用new Vue以后,Header多了一個(gè)Ctor屬性,這個(gè)屬性緩存了Header組件對(duì)應(yīng)的構(gòu)造函數(shù)。后續(xù)調(diào)用new Vue(TheHeader)時(shí),使用的構(gòu)造函數(shù)始終都是第一次緩存的,因此title的值也不會(huì)發(fā)生變化。Vue源碼對(duì)應(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 }
找到了原因,我們會(huì)發(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方法時(shí),我們都將緩存的構(gòu)造函數(shù)去掉即可。
問(wèn)題三、是否可以不使用Vue.extend
實(shí)測(cè)其實(shí)不使用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來(lái)創(chuàng)建實(shí)例相較extend創(chuàng)建實(shí)例來(lái)說(shuō),不會(huì)在Header.vue中緩存Ctor屬性,相較來(lái)說(shuō)是一個(gè)更好的辦法。但是之前有看過(guò)Vant實(shí)現(xiàn)Toast組件,基本上是使用Vue.extend方法而沒(méi)有直接使用Vue,這是為什么呢?
總結(jié)
到此這篇關(guān)于Vue插件實(shí)現(xiàn)過(guò)程中遇到問(wèn)題的文章就介紹到這了,更多相關(guān)Vue插件實(shí)現(xiàn)問(wèn)題內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Vue3對(duì)比Vue2的優(yōu)點(diǎn)總結(jié)
vue3解決了vue2的一些缺陷與弊端,學(xué)習(xí)新的技術(shù)是很有必要的,本文總結(jié)了一些vue3的優(yōu)點(diǎn),希望各位能盡快轉(zhuǎn)入vue3的使用中2021-06-06Ant Design Vue Pro動(dòng)態(tài)路由加載,服務(wù)器重啟首頁(yè)白屏問(wèn)題
這篇文章主要介紹了Ant Design Vue Pro動(dòng)態(tài)路由加載,服務(wù)器重啟首頁(yè)白屏問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-10-10vue項(xiàng)目netWork地址無(wú)法訪問(wèn)的問(wèn)題及解決
這篇文章主要介紹了vue項(xiàng)目netWork地址無(wú)法訪問(wèn)的問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-09-09vue3中獲取dom元素和操作實(shí)現(xiàn)方法
ref是Vue3中一個(gè)非常重要的功能,它可以用來(lái)獲取DOM節(jié)點(diǎn),從而實(shí)現(xiàn)對(duì)DOM節(jié)點(diǎn)的操作,下面這篇文章主要給大家介紹了關(guān)于vue3中獲取dom元素和操作實(shí)現(xiàn)的相關(guān)資料,需要的朋友可以參考下2023-06-06vue中定義全局聲明vscode插件提示找不到問(wèn)題解決
這篇文章主要為大家介紹了vue中定義全局聲明vscode插件提示找不到問(wèn)題解決,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05