大家一定會覺得這個標題很奇怪,不就都是 new Vue
嗎?是有什麼差別?對啊,其實都是 Vue 沒錯,但是就像是斯斯有兩種,Vue 也是有好多種不同面向。
是說現在斯斯好像不只兩種了。
new Vue
通常,我們在實作一個 App 的時候,最終我們使用了 new Vue
來將整個 App 綁定在某一個 DOM 的元件上。如果,你綁定一次不夠,想要綁定兩次呢?也不是不可以,
import Vue from 'vue'
import App1 from './App1.vue'
import App2 from './App2.vue'
new Vue({
render: h => h(App1)
}).$mount('#app1')
new Vue({
render: h => h(App2)
}).$mount("#app2')
雖然不是很好的例子,但實際上也是可以這樣操作的。但是,基於什麼樣的理由需要這樣操作呢?這樣操作的情況下,又會衍生出什麼狀況?
- 當你的區塊很邊緣的時候。
- 這兩個 Vue App 有各自獨立的生態。
- 這兩個 Vue App 建議透過 EventBus 來溝通。
- 如果共用 Store 或是 Router 最好有互相干擾的心理準備。
- 原本使用 全域 的設定,兩個 App 都可以看到。
倘若,你將這兩個 App 分成兩個入口,也就是 main1.js
與 main2.js
的話,還會有更多問題:
- 這兩個 Vue App 是互相獨立的個體。
- 這兩個 Vue App 並無有效溝通方式。
- 你的 全域 不一定是我的 全域。
- 如果要互相溝通,需要把層級再往上提升,甚至放到
window
這一層。
現在會有多少人這樣做呢?
沒有(吧)?
在沒有 Webpack 工具下
主要的命題在此。我們不一定每次經手一個專案,都要做一大堆前置作業。那麼,在沒有這些工具的情況下,我們是不是就沒辦法寫 Vue 了?
對啊(欸等等)!
最簡單的方式,我們這樣就可以開始使用 Vue 了,
<script src="https://cdn.jsdelivr.net/npm/[email protected]"></script>
然後你的寫法就會變成,
new Vue({
template: '<div>{{ msg }}</div>',
data: function () {
return {
msg: 'HelloWorld'
}
}
})
然後就覺得,我靠!這樣超難寫的!我要回去用 Vue CLI 建立環境了(住手!) 我們這邊的大前提就是,在 沒有 Webpack 這一類的工具下,所以神說沒有 Webpack 就是沒有 Webpack,沒得含扣。
所以,在這種 極端 環境底下,我們到底要怎麼製作 Vue App?我們該怎麼製作我們的元件?
放棄不可恥,但是有用!
首先,你必須要知道幾件事情:
- 你的 Vue 不能用 Runtime-only 的套件。
- 第三方套件不一定可以直接使用。
- 你可以使用 EventBus 來溝通。
我們可以直接用例子來看,
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>ITHome 2019</title>
</head>
<body>
<div id="app">
<h1>{{ msg }}</h1>
</div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]"></script>
<script>
(function() {
new Vue({
data: function () {
return {
msg: 'Hello World'
}
}
}).$mount('#app');
})();
</script>
</body>
</html>
這樣看起來好像沒什麼大問題,如果我們有其他的元件要載入使用呢?
Vue.component(
'MyComponent',
{
name: 'MyComponent',
template: '<div class="my-component">{{ msg }}</div>',
data: function () {
return {
msg: 'Hello My Component'
}
}
}
)
然後我們就能在 HTML 裡面使用這個 <my-component></my-component>
元件了。
元件載入問題
我們又要來聊元件載入的問題了。上述的例子你會發現 template
的地方是直接寫在 <script>
裡面,那麼,我們每次要更新的時候,是不是就變得相當麻煩。那麼我們可以怎麼做呢?
- 把元件獨立到一個 JavaScript 檔案內。
- 把
template
的部分分開來寫,寫到一個.html
檔案裡面。 - 利用 XHR 的方式讀取
.html
的資料,把讀取的資料放到template
裡面。
具體的方式怎麼做呢?首先,我們可以拿 axios
這個工具來用,
axios({
method: 'get',
url: './templates/mycomponent.html',
responseType: 'text'
}).then(function (res) {
Vue.component(
'MyComponent',
{
template: res.data,
data: function () {
return {
msg: 'Hello My Component'
}
}
}
)
})
然後這個檔案我們把他儲存成 ./js/mycomponent.js
之後,再把他放到 index.html
裡面。然後原本的 template
的部分,將他放到 ./templates/mycomponent.html
,這樣我們就不需要改 JavaScript 的檔案,直接改樣版檔案即可。
有沒有 MVC 的錯覺。
所以,我們一開始可能會是這樣,
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>ITHome 2019</title>
</head>
<body>
<div id="app">
<h1>{{ msg }}</h1>
<my-component></my-component>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.0/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]"></script>
<script src="./js/mycomponent.js"></script>
<script>
(function () {
new Vue({
data: function () {
return {
msg: 'Hello World'
}
}
}).$mount('#app');
})();
</script>
</body>
</html>
這個時候你再回到瀏覽器,發現 my-component
怎麼不會出現?原因在於你的 mycomponent.js
當中,是使用 XHR 的方式來讀取樣版檔案。所以,當你的 App 在啟動的時候,你的 XHR 可能還在讀取,所以這個時候,你的 my-component
其實並沒有在 Vue 的全域元件當中。
這樣一來,你就必須要先確保樣版檔案已經讀取進來了,才能將你的 App 啟動。這個時候,我們可以利用 Vue 自身,製作一個 EventBus 來當作監聽工具。所以,我們這個監聽工具需要做哪些事情呢?
- 打開一個監聽接口,我們叫做
componentLoaded
代表元件被讀取進來了。 - 當監聽接口被觸發之後,我們這個時候才將 Vue App 做初始化啟動。
window.EventBus = new Vue();
window.EventBus.$on(
'componentLoaded',
function () {
new Vue({
data: function () {
return {
msg: 'Hello World'
}
}
}).$mount('#app');
}
);
然後我們的 mycomponent.js
在讀取完樣版之後,要觸發 我已經讀取完畢 的動作。
// ...前略
window.EventBus.$emit('componentLoaded');
這麼一來,我們的元件看起來就很正常顯示了。
那麼,如果我們有很多元件,巢狀元件的時候該怎麼辦?
Promise.all
是你的好伙伴。
也許你們會覺得很奇怪,為何會有這種奇怪的需求?就如同我剛剛的大前提,當你沒有 Webpack 的時候,這就是一種比較詭異,但是還算是合理的解決方案。只是說,如果我是開發完之後,把 dist
給客戶就好,那麼,好像也不必真的那麼麻煩。
其實在這個系列文後面還是有提到動態載入的事情,只是最近這幾篇文章好像都快講完了?
事情當然不是你們所想的那麼簡單
雖然說我後面會提到 Vue-Router 的部分,但是,這邊先偷偷給大家看一個,搭配 Router 動態載入的實例:
let router = new VueRouter({
routes: [
{
path: '/',
name: 'homepage',
component: { template: '<router-homepage></router-homepage>' },
props: true,
meta: {
name: '首頁'
}
}
]
});
router.beforeEach(function(to, from, next) {
let template = 'not-found';
if (typeof to.name !== 'undefined' && to.name !== null) {
template = to.name;
}
return generateTemplateLoader(template).then(function() {
next();
});
});
至於那個 generateTemplateLoader
的地方呢?我們這邊就賣個關子吧。等到大概第 21, 22 篇的時候我們再來聊聊。
小結
或許你會覺得,怎麼一直都在講動態載入。我只能說,一旦遇上了你還是得想出各種花招來滿足一些需求。然後,這些所謂 動態載入 都是從奇怪的需求衍生而來的。
可以的話,我也想每次都 VUE CLI 來做啊(苦笑)。