Vue 2 升級 Vue 3 之全域元件註冊問題
| | 3 | | ![]() |
在我專案裡 Vue.js 主要用來處理 MVVM,用 <script> 載入 vue.js,寫幾行 JavaScript 搞定,走不寫模組,不用 TypeScript,免編譯打包的「輕前端」模式,但常用邏輯還是會寫成元件(Component)方便共用。
在 Vue 2 時代,我習慣在網頁共用的 .js 裡使用 Vue.component("my-component", ...) 註冊元件,註冊一次,各網頁不需宣告就能使用元件。
正式擺脫 IE 後,終於不用再死守 Vue 2,試著將一個小專案升級到 Vue 3,遇到小麻煩。
Vue 3 改變了元件註冊方式,不再提供全域註冊,必須先 Vue.createApp() 建立實體,app.component("my-component", ...) 註冊或用 components 屬性引用才能使用元件。參考:重新認識 Vue.js - 元件的宣告與註冊
全域元件改成區域元件可減少程式間互相干擾,在軟體架構來說是正確的方向,但對簡單應用來說(例如:程式很單純,全域元件打架機率趨於零的場合),這番調整讓元件註冊變得繁瑣。
用個範例來說明,原本 Vue 2 做法是在 my-components-vue2.js 中註冊多個元件:
Vue.component('date-tag', {
template: '<div>{{date}}</div>',
data: function () {
return {
date: new Date().toJSON().slice(0, 10)
};
}
});
Vue.component('time-tag', {
template: '<div>{{time}}</div>',
data: function () {
return {
time: new Date().toJSON().slice(11, 19)
};
}
});
Vue.component('host-tag', {
template: '<div>{{host}}</div>',
data: function () {
return {
host: location.host
};
}
});
如此,數十支 HTML 只需載入 my-components-vue2.js 便能在網頁使用 <date-tag> <time-tag> 插入元件。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.10/dist/vue.js"></script>
<script src="my-components-vue2.js"></script>
</head>
<body>
<div id="app">
<date-tag></date-tag>
<time-tag></time-tag>
<host-tag></host-tag>
</div>
<script>
var app = new Vue({
el: '#app'
});
</script>
</body>
</html>
升級 Vue 3 之後,元件 .js 跟網頁都要做一些修改:
var dateTag = {
template: '<div>{{date}}</div>',
data: function () {
return {
date: new Date().toJSON().slice(0, 10)
};
}
};
var timeTag = {
template: '<div>{{time}}</div>',
data: function () {
return {
time: new Date().toJSON().slice(11, 19)
};
}
};
var hostTag = {
template: '<div>{{host}}</div>',
data: function () {
return {
host: location.host
};
}
};
建立 app 寫法改為 Vue.createApp,並在宣告中透過 components 列舉要註冊的物件:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="https://unpkg.com/vue@3"></script>
<script src="my-components-vue3-upgrade.js"></script>
</head>
<body>
<div id="app">
<date-tag></date-tag>
<time-tag></time-tag>
<host-tag></host-tag>
</div>
<script>
var app = Vue.createApp({
components: {
'date-tag': dateTag,
'time-tag': timeTag,
'host-tag': hostTag
}
// 若元件物件名稱與標籤相符,可簡寫成
// components: { dateTag, timeTag, hostTag }
})
.mount('#app');
</script>
</body>
</html>
假設我有 30 個網頁,30 個 app 都要加 components: { dateTag, timeTag, hostTag },未來若新增其他元件,所有 components 列舉都要改,這是標準的「Copy & Paste 負面教材」呀,一想就覺得很不 OK 呀。
這種情境,就是套件(Plugin,也有人翻成插件、外掛)上場的時刻。最簡單的套件寫法是寫一個 function,接收 app 及 options 參數,內部呼叫 app.component(...) 逐一註冊元件,options 則是自訂參數,可用來決定要註冊哪些元件或變更元件設定值,提高運用彈性。以下是簡單示範:
function myComponentsPlugin(app, options) {
app.component('date-tag', {
template: '<div>{{date}}</div>',
data: function () {
return {
date: new Date().toJSON().slice(0, 10)
};
}
});
app.component('time-tag', {
template: '<div>{{time}}</div>',
data: function () {
return {
time: new Date().toJSON().slice(11, 19)
};
}
});
app.component('host-tag', {
template: '<div>{{host}}</div>',
data: function () {
return {
host: location.host
};
}
});
}
如此,呼叫端可簡化為 .use(myComponentsPlugin),不需把所有元件列出來,未來要新增元件,修改 my-components-vue3.js 即可,保留原先全域元件的簡單方便:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="https://unpkg.com/vue@3"></script>
<script src="my-components-vue3.js"></script>
</head>
<body>
<div id="app">
<date-tag></date-tag>
<time-tag></time-tag>
<host-tag></host-tag>
</div>
<script>
var app = Vue.createApp({
//...
}).use(myComponentsPlugin)
.mount('#app');
</script>
</body>
</html>
再學到一些經驗。
Vue 3 doesn't support registering components globally, this article demostrating how to use plugin to make components registration easier.
Comments
# by wen
請問用 myComponentsPlugin 雖然能引入共用組件,但其組件樣式是如何控管呢? 還是都集中寫在一隻 css 內
# by Jeffrey
to wen, 依我的理解,要做到 css 獨立管理,要走向 SFC,透過編譯整合 https://book.vue.tw/CH3/3-2-vue-sfc.html
# by Rexwu
今天剛好也在做類似的事情 分享一下作法 ```=js # my-components-vue3.js window.VCOMP = { dataTag:{ template: '<div>{{date}}</div>', data: function () { return { date: new Date().toJSON().slice(0, 10) }; } }, //其他的組件... } ``` ``` #index.html var app = Vue.createApp({ components: window.VCOMP // 若元件物件名稱與標籤相符,可簡寫成 << 因這一行的參考有的想法 // components: { dateTag, timeTag, hostTag } }) .mount('#app'); ```