這是關於元件方法的最後一篇,實際上來說,Vue.options
並不能算是很魔術的方法。如果把他歸類為奇技淫巧可能比較適合(政治正確)。由於元件的註冊過程當中,會將相對應的實體放到這個屬性當中,所以,我們就可以利用這個地方來做一些事情。
身為邊緣人,我最喜歡這種邪門歪道了。
App 實體 a.k.k Vue 替身
我們在使用 Vue 的時候,在最後一定是使用 new Vue
之後再 $mount
到某個 DOM 底下。
import Vue from 'vue'
import App from './App.vue'
new Vue({
render: h => h(App)
}).$mount('#app')
這一整個 Vue 實體會包含了你所使用到的相關資訊,你可以在 mounted
的時候將他印出來,底下這個例子就是在 App.vue
的地方,將 this
印出來的結果。
然後你會發現,奇怪了,這邊有一個叫做 $options
的東西,但是沒有叫做 options
的屬性啊?沒關係,我們繼續往下看,他其實是藏在 __proto__
裡面的 constructor
,
我們把 Vue 這個原型展開之後,你就會看到裡面有一個 options
,底下有包含了不少東西:
components
當前有幾個元件載入了。name
當下的元件名稱。data
當下元件所設定的預設資料集。directives
,filters
你在當下元件當中是否有設定過濾器或是自定義指令。beforeCreate
,beforeDestroy
,mounted
你在當下元件中,所設定過的相對應方法會出現在這些地方。render
你所使用的渲染函式。staticRenderFns
這個很特別,是用來作一些渲染優化或是快取的。這個解釋起來可能可以在開個兩篇文章來討論,可以參考老爸的 Meetup 說明。
好的,那麼可能你會覺得,這個 options
好像哪裡怪怪的?是的,這裡的 options
其實只是 App
這個根元件的相關資訊。跟我們所提到的 Vue.options
到底,實際上的差異是什麼?
一言不合就
console.log
import Vue from 'vue'
export default {
name: 'App',
mounted () {
console.log(Vue.options)
}
}
你會在 components
的區塊裡面看到一些元件,這裡特別說明一下,那個 Helper
是我自己做的,至於為什麼會出現在這裡?而為何 App
又沒有出現在這裡?我們底下會繼續說明這個部分。
Vue 真正的實體
就如同我上面的例子,你在 console.log(Vue.options)
的時候,其實是把 Vue 這個根元件給印出來。但是,你可能會覺得奇怪的部分,在於:
為何這個元件沒有包含
App
?
首先,這個 Vue 其實是一個沒有被初始化的元件實體,你可以把他想成是 Vue 元件的地基,所有的 Vue 元件都是根據這個地基而來的。但,由於 App
是你使用 new Vue
所產生出來的,所以,在這個地方就不會包含 App
或是你往後所使用的任何元件。
那個
Helper
是怎麼放進去的?
還記得 Vue 有所謂 全域 的設定這件事情嗎?
Vue.component('my-component', { ... })
你覺得最前面的那個 Vue
會是誰呢?是的,他就是你在這整個應用程序中,每次在開頭所載入的 Vue 這個物件。
import Vue from 'vue' // 這裡就是所謂的「那個」Vue
export default {
name: 'App'
}
所以說,這個 Vue 裡面,就會有 全域 的相關設定。那麼,我們就能夠從這邊,去拿到一些,使用全域設定所綁定進來的東西,就上面的結構來看,會有元件、自訂指令以及過濾器。那麼,利用這一點,我們就可以從 Vue.options
裡面的資料來做一些事情。
另類的動態載入
對啊,我們又回到動態載入這個議題了。只是這一次我們的對象換了,換成了對於 全域 物件的操作。這麼做會不會有什麼衍生問題?我這邊提出幾個點給大家思考一下。
- 既然叫做 全域,會不會有人跟你用一樣的名字?
- 如果我並不是使用 Webpack 或是相關封裝工具,Vue 是否會有其他風險?
- 若是載入元件檔案,會不會因為檔案不存在而讓 App 整組壞掉?
- 放在這裡的東西要怎麼跟別人溝通?
以上問題我都不會回答,你可以等到踩到雷的時候再來問我(燦笑)。
請注意!使用這種動態載入的方式,並不是什麼正規技巧,繼續往下看之前,請先詳閱公開說明書。
首先,我先給大家一個比較完整的範例,來示範一下這個奇技淫巧的正確姿勢。
import HelloKitty from '@/components/HelloKitty.vue'
import HelloWorld from '@/components/HelloWorld.vue'
Vue.component('HelloKitty', HelloKitty)
Vue.component('HelloWorld', HelloWorld)
上面這個檔案內容,我們假設叫做 myComponent.js
,並且把他放在 extends
目錄底下。然後我們的 App.vue
裡面的 <script>
,就換成這種寫法。
import Vue from 'vue'
export default {
name: 'App',
data () {
return {
myComponent: 'HelloKitty',
loadedComponent: null
}
},
created () {
if (typeof Vue.options.components[this.myComponent] !== 'undefined') {
this.loadedComponent = Vue.options.components[this.myComponent]
}
}
}
接著,我們的應用程式入口 main.js
那邊需要調整一下。
import Vue from 'vue'
import App from './App.vue'
require('./extends/myComponent.js')
new Vue({
render: h => h(App)
}).$mount('#app')
最後執行結果:
我這邊總結一下我們到底做了什麼樣的事情:
- 我們把
components
都拿掉了。 - 我們把元件都搬去了
Vue.component
那邊去做。 - 透過
main.js
把元件require
進來。 App.vue
透過:is
的方法來做到元件替換。
或許多數人會覺得,那跟我在 App.vue
裡面把元件都 import
進來到底有什麼差異?而且把他放到 extends
裡面不就更難維護了嗎?
對!
我這個人憨慢講話,但是我金實
(機)在(歪)
這邊如果在這種小型專案中,確實是看不出什麼好處。那麼,我現在把這個專案結構 放大 10 倍 來看看。
- 網站有 10 種樣版。
- 每一種樣版有獨立的
header
,main
與footer
。
好的,當你的 PM 不能叫客戶回家吃自己的時候,那我離職總可以吧!
不行!
所以,如果依照正規的作法,我們應該會這樣做。
<template>
<div id="#app">
<header :is="headerComponent"></header>
<main :is="mainComponent"></main>
<footer :is="footerComponent"></footer>
</div>
</template>
然後我們的 <script>
部分,就放了很多元件載入的動作。這邊絕對不是要充字數,是的話我就會把 main
跟 footer
一樣都打 10 次了(欸)。
import Header0 from '@/components/header0.vue'
import Header1 from '@/components/header1.vue'
import Header2 from '@/components/header2.vue'
import Header3 from '@/components/header3.vue'
import Header4 from '@/components/header4.vue'
import Header5 from '@/components/header5.vue'
import Header6 from '@/components/header6.vue'
import Header7 from '@/components/header7.vue'
import Header8 from '@/components/header8.vue'
import Header9 from '@/components/header9.vue'
import Main0 from '@/components/main0.vue'
// 以下類推
import Footer0 from '@/components/footer0.vue'
// 以下類推
export default {
name: 'App',
data () {
return {
headerComponent: null,
footerComponent: null,
mainComponent: null
}
},
created () {
this.headerComponent = Header0
this.footerComponent = Footer0
this.mainComponent = Main0
}
}
然後,你每次要修改,或是新增,或是做什麼其他的事情,你就是得改這個 App.vue
才行。然後,每次打包出來的就是這麼一大包。像是我自己的 2012 MacBook Pro,每次要打包這麼多檔案,看著看著我自己都 褲底一包。
小結
上面那個例子,放到 main.js
使用 require
是一種解法。但是,你想想每次再開發的時候,都要載入這麼多檔案不覺得煩悶?當然,如果你是 2019 的 Mac 可能就不覺得,
有錢人的生活往往就是這麼樸實無華,且枯燥。
還是有更取巧的作法,我們後面再慢慢聊。