在我專案裡 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'); ```

Post a comment