您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關Vue插件實現過程中遇到的問題有哪些,小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。
最近做H5遇到了一個場景:每個頁面需要展示一個帶有標題的頭部。一個實現思路是使用全局組件。假設我們創建一個名為TheHeader.vue的全局組件,偽代碼如下:
<template> <h3>{{ title }}</h3> </template> <script> export default { props: { title: { type: String, default: '' } } } </script>
創建好全局組件后,在每個頁面組件中引用該組件并傳入props中即可。例如我們在頁面A中引用該組件,頁面A對應的組件是A.vue
<template> <div> <TheHeader :title="title" /> </div> </template> <script> export default { data() { title: '' }, created(){ this.title = '我的主頁' } } </script>
使用起來非常簡單,不過有一點美中不足:如果頭部組件需要傳入的props很多,那么在頁面組件中維護對應的props就會比較繁瑣。針對這種情況,有一個更好的思路來實現這個場景,就是使用Vue插件。
同樣是在A.vue組件調用頭部組件,使用Vue插件的調用方式會更加簡潔:
<template> <div /> </template> <script> export default { created(){ this.$setHeader('我的主頁') } } </script>
我們看到,使用Vue插件來實現,不需要在A.vue中顯式地放入TheHeader組件,也不需要在A.vue的data函數中放入對應的props,只需要調用一個函數即可。那么,這個插件是怎么實現的呢?
它的實現具體實現步驟如下:
創建一個SFC(single file component),這里就是TheHeader組件
創建一個plugin.js文件,引入SFC,通過Vue.extend方法擴展獲取一個新的Vue構造函數并實例化。
實例化并通過函數調用更新Vue組件實例。
按照上面的步驟,我們來創建一個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,就完成了插件實現的全部邏輯過程。不過,盡管這個插件已經實現了,但是有不少問題。
如果我們在單頁面組件中使用,只要使用router.push方法之后,我們就會發現一個神奇的問題:在新的頁面出現了兩個頭部組件。如果我們再跳幾次,頭部組件的數量也會隨之增加。這是因為,我們在每個頁面都調用了這個方法,因此每個頁面都在文檔中放入了對應DOM。
考慮到這點,我們需要對上面的組件進行優化,我們把實例化的過程放到插件外面:
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)
這樣處理,雖然還是會重復在文檔中插入DOM。不過,由于是同一個vue實例,對應的DOM沒有發生改變,所以插入的DOM始終只有一個。這樣,我們就解決了展示多個頭部組件的問題。為了不重復執行插入DOM的操作,我們還可以做一個優化:
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)
增加一個變量來控制是否已經插入了DOM,如果已經插入了,就不再執行插入的操作。優化以后,這個插件的實現就差不多了。不過,個人在實現過程中有幾個問題,這里也一并記錄一下。
在實現過程中突發奇想,是不是可以直接修改TheHeader組件的data函數來實現這個組件呢?看下面的代碼:
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)
看上去也沒什么問題。不過實踐后發現,調用$setHeader方法,只有第一次傳入的值會生效。例如第一次傳入的是'我的主頁',第二次傳入的是'個人信息',那么頭部組件將始終展示我的主頁,而不會展示個人信息。原因是什么呢?
深入Vue源碼后發現,在第一次調用new Vue以后,Header多了一個Ctor屬性,這個屬性緩存了Header組件對應的構造函數。后續調用new Vue(TheHeader)時,使用的構造函數始終都是第一次緩存的,因此title的值也不會發生變化。Vue源碼對應的代碼如下:
Vue.extend = function (extendOptions) { extendOptions = extendOptions || {}; var Super = this; var SuperId = Super.cid; var cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {}); if (cachedCtors[SuperId]) { // 如果有緩存,直接返回緩存的構造函數 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構造函數的地方 return Sub }
找到了原因,我們會發現這種方式也是可以的,我們只需要在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)
每次執行$setHeader方法時,我們都將緩存的構造函數去掉即可。
實測其實不使用Vue.extend,直接使用Vue也是可行的,相關代碼如下:
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來創建實例相較extend創建實例來說,不會在Header.vue中緩存Ctor屬性,相較來說是一個更好的辦法。但是之前有看過Vant實現Toast組件,基本上是使用Vue.extend方法而沒有直接使用Vue,這是為什么呢?
關于“Vue插件實現過程中遇到的問題有哪些”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,使各位可以學到更多知識,如果覺得文章不錯,請把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。