情境

我們可能需要為 Vue 專案添加自定義 method,重點是每個 component 都有可能用到,有什麼方法可以不用一直複製貼上?

例如原生 JS 操作瀏覽器 cookie 總是很囉唆,可以用幾個 function 包起來用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function setCookie(name, value, expireDays){
let exdate = new Date();
exdate.setDate(exdate.getDate() + expireDays);
document.cookie = `${name}=${escape(value)}; expires=${ !expireDays ? ''
: exdate.toGMTString()};`;
};

function getCookie(name){
var reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)'),
arr = document.cookie.match(reg);
return arr ? arr[2] : null;
};

function delCookie(name){
let exp = new Date();
exp.setTime(exp.getTime() - 1);
const value = getCookie(name);
if (value != null)
document.cookie = `${name}=${value}; expires=${exp.toGMTString()}`;
};

看來可以當 Vue method 了,但難道我一個 component 用到一次就要再寫一次嗎?直覺想到ES6的import好像有辦法用,那要如何跟 Vue instance 好好合作呢?

寫成 Mixins

Mixins 能提供 Vue instance 屬性定義的復用。若 component 的 method 與 mixin 的衝突,會以 component 為主。

Mixins 不只能混入 method,還包括 data, lifecycle-hook 等其他屬性,但 merge 後的行為不盡相同,像是created()mixin 後比較像堆疊而不是覆寫,有興趣可以翻翻文件

建立一個 export object 的 js 檔,把自定義 method 寫入 methods 屬性裡:

cookieMixin.js
1
2
3
4
5
6
7
8
9
10
11
export const cookieMixin = {
methods: {
setCookie: function(name, value, expireDays){ ... },
getCookie: function(name){ ... },
delCookie: function(name){
...
const value = this.getCookie(name);
...
},
}
}

在 component 使用 mixin

只要引入 js 檔並用Array assign 給 mixins 屬性:

ComponentA.vue
1
2
3
4
5
import {cookieMixin} from 'cookieMixin.js'
export default {
mixins: [cookieMixin],
...
}

如此這個 component 便能使用 this.setCookie()等 method。

Global mixin

若想在每一個 component 中引用,不用每個文件都寫個 import,而是直接在入口 JS 文件定義:

main.js
1
2
import {cookieMixin} from 'cookieMixin.js'
Vue.mixin(cookieMixin);

注意 global mixin 會影響所有Vue instance,包括第三方引入的 component,用於 data 或 lifecycle-hook 更是要謹慎設計。

使用Vue.prototype

另一種方法,利用JS的原型鏈將自定義 method 掛到 Vue instance上,在入口 JS 文件定義:

main.js
1
2
3
4
5
6
7
Vue.prototype.$setCookie = function(name, value, expireDays){ ... },
Vue.prototype.$getCookie = function(name){ ... },
Vue.prototype.$delCookie = function(name){
...
const value = this.$getCookie(name);
...
},

如此一來不需要用 mxixns,各 component 也能直接呼叫 this.$setCookie(...)

原型鏈上自定義 method 加錢號$的原因,是避免與 component 中 this 所 proxy 的 data, computed, method 等命名空間互衝突,也聲明其特殊性,算是開發潛規則

嗯?錢號? this.$storethis.$routerうっ、頭が

這種熟悉感不是巧合,Vue 生態系的套件就是以此套路開發的,只是再包成 Vue plugin 使用,寫到這覺得 Vue 真的是很優雅靈活,各種應用游刃有餘又不會賣弄艱澀語法,佩服作者的設計與發揮 JS 特性能力

包成 Plugin

既然都講到這地步了,乾脆把自定義 method 做成一個 vue-cookie plugin ( Github 真的有這個 plugin )

整理一下 code,其實可以用 object 比較乾淨:

1
2
3
4
5
6
7
8
9
Vue.prototype.$cookie = {
set: function(name, value, expireDays){ ... },
get: function(name){ ... },
del: function(name){
...
const value = this.$cookie.get(name);
...
},
}

建立一個 vue-cookie.js , export 輸出 install(Vue, options) 這個 function,Vue 作為參數帶入,然後將 method 寫進 install 裡:

vue-cookie.js
1
2
3
4
5
export default {
install(Vue, options) {
Vue.prototype.$cookie = { ... };
}
}

入口 js 文件 import 進來就是個 plugin,使用Vue.use 載入 :

main.js
1
2
import VueCookie from './vue-cookie.js'
Vue.use(VueCookie);

就能在 App 中使用 this.$cookie.set() 了。搞不好能放到 npm 給別人用呢~