Vue 之 Component
Component 概念為 React 所發明,讓我們可以重複使用 HTML,Vue、Angular 也 致敬
React,採用 component ,至此 3 大 Framework 都統一採用 Component-based 架構。
Version
Vue 2.5.17
Architecture
Introduction
Vue Instance 有自己的 data
、methods
、computed
、watch
,但 Vue Instance 只是 HTML 的代言人,讓我們在 JavaScript 控制 HTML,但若要 重複使用
,就不是那麼方便,這時我們需要的是 Vue Component。
除此之外,Vue Component 也讓我們在開發時實踐 Divide and Conquer
哲學,先將需求切成小小 component,然後一一擊破,最後再將 component 組合起來,如此 component 也更加 單一職責
,更 容易維護
與 重複使用
。
Global Component
之前我們只會 MVVM 的 Hello World,若改用 Component-based 的寫法呢 ?
index.html
1 | <!DOCTYPE html> |
第 9 行
1 | <hello-world></hello-world> |
由原本的 <span>Hello World</span>
,變成自訂的 hello-world
tag。
我們會為自己的的 Vue Component 定義自己的 HTML tag
index.js
1 | new Vue({ |
建立 Vue Instance。
HelloWorld.js
1 | Vue.component('HelloWorld', { |
定義 HelloWorld
component。
使用 Vue Vue.component()
定義 Vue Component。
- 第 1 個參數:string,傳入自訂的 HTML tag 名稱
- 第 2 個參數:object,類似傳入 Vue Instance 的 constructor 參數
Vue Component 的使用,有幾點要注意:
- Vue 規定 Vue Component 一定要定義在 Vue Instance
之前
,否則 Life Cycle 在compile HTML
階段,會不知道 Vue Component 所自訂的 HTML tag - 自訂的 HTML tag 名稱,無論使用 camelCase,CamelCase,最後 Vue 都會改用 Kebab-case (全小寫,單字間以
-
隔開),這是 W3C 所建議,且必須是 2 個單字,避免用一個單字與 HTML 預設 tag 重複 - 關於 Component (HTML tag) 與 JavaScript 檔案命名方式,Vue 官方的 Style Guide :
- CamelCase:
HelloWorld
、HelloWorld.js
、HelloWorld.vue
- kebab-case:
hello-world
、hello-world.js
、hello-world.vue
- Vue CLI 使用 CamelCase
- CamelCase:
Local Component
使用 Vue.component()
所宣告的是 Global Component,也就是每個 Vue Instance 都可使用,若你想定義只有某個 Vue Instance 能使用的 component,則要使用 Local Component。
inex.html
1 | <!DOCTYPE html> |
HTML 部分一樣不變使用 <hello-world></hello-world>
。
index.js
1 | new Vue({ |
在傳入 Vue Instance 的 constructor 參數內,加上 components
Property,為 object。
以自訂的 HTML tag hello-world
為 key,將 Vue.component()
第二個參數的 object 為 value。
MVVM vs. Component
目前 Vue Component 的 data 顯示都是寫死的,我們知道 MVVM 的精髓就是 Data Binding,要如何將 MVVM 與 Component-based 兩種架構合而為一呢 ?
index.html
1 | <!DOCTYPE html> |
第 9 行
1 | <my-counter></my-counter> |
使用自訂的 <my-counter></my-counter>
。
index.js
1 | new Vue({ |
建立 Vue Instance。
MyComponent.js
1 | Vue.component('MyCounter', { |
第 2 行
1 | template: ` |
將 HTML Template 宣告在 template
property 下。
由於 HTML Template 在實務上會很多行,用普通字串不方便,建議改用 ECMAScript 2015 的 string template,就不必再
字串相加
了
第 9 行
1 | data() { |
data
部分,由原本 Vue Instance 的 data
property 改成 data()
function。
改回傳 data
object。
14 行
1 | methods: { |
對 data
內的 counter
累加。
Q : 為什麼寫成 Vue Component 後,要從
data
property 改成data()
function ?
1 | data: { |
若改成 data
property 寫法,Vue 會無法執行,且出現 warning。
在正統 OOP,兩個 component 應該是兩個 instance,而 data
包在 instance 內,因此 Component 間的 data
不會互相影響,也就是 OOP 的 封裝
。
但 Vue 底層並不是採用 OOP 方式,而是共用同一份 component instance,只有 data
是不同份。
這也是為什麼為什麼 Vue 要你改用 data()
function,而且是回傳全新對 data
object。
只要寫 Vue Component,就一定要改用
data()
function,不能使用data
property
使用 Vue Component 時,還有一點值得注意:
- 不可使用 HTML self closing 語法
1 | <div id="app"> |
這種寫法,Vue 不會出錯,但只有一個 component 能動。
1 | <div id="app"> |
要這樣寫,Vue 才能正常執行。
Component vs. DOM Parser
有時候在使用 Vue Component 時,會發現無法如預期顯示在 Browser 裏。
index.html
1 | <!DOCTYPE html> |
第 9 行
1 | <select> |
在 <select></select>
內使用自訂的 <my-option></my-option>
Vue Component。
index.js
1 | new Vue({ |
建立 Vue Instance。
MyOption.js
1 | Vue.component('MyOption', { |
自訂的 MyOption
只包含 <option>Vue</option>
部分。
- Chrome 無法正常顯示
<select></select>
以下沒有任何<option></option>
這牽涉到各 Browser 的 DOM Parser 如何解析 HTML。
以 Chrome 而言,它認為 <select></select>
下 只應該
是 <option></option>
,其他的 HTML tag 都為非法,因此忽略不使用。
DOM Parser 會因 Browser 而異
index.html
1 | <!DOCTYPE html> |
由本來的 <my-option></my-option>
改成 <my-select></my-select>
。
index.js
1 | new Vue({ |
建立 Vue Instance。
MySelect.js
1 | Vue.component('MySelect', { |
連 <select>
一起包進 Vue Component,如此 Chrome 就無法干涉 <option>
。
Dynamic Component
先定義好 Vue Component,然後動態切換 component。
index.html
1 | <!DOCTYPE html> |
12 行
1 | <component :is="content"></component> |
使用 Vue 擴充的 <component></component>
,綁定其 is
,當 content
指定什麼 Vue Component 時,<component></component>
就會動態切換該 Vue Component。
並沒有在 HTML 內事先使用特定 Component tag,只使用
<component></component>
保留其動態彈性
index.js
1 | new Vue({ |
第 3 行
1 | data: { |
在 data
內定義 content
model,其中預設值 my-lessons
為 component 名稱。
第 6 行
1 | methods: { |
由 method 動態改變 content
,MVVM 會再動態改變 <component></component>
的 :is
,達到動態組件的需求。
MyLessions.js
1 | Vue.component('MyLessons', { |
定義了 MyLessions
component。
MyApply.js
1 | Vue.component('MyApply', { |
定義了 MyApply
component。
但這兩個 Vue Component 都沒在 HTML 內被使用,將由 JavaScript 動態指定
Keep-Alive
由於 <component></component>
類似 v-if
,其 Dynamic Component 是藉由 刪除 DOM element,並建立新的 DOM element
的方式,所以原本的 user 輸入的資料,也會一併被刪除。
若要保留原本 user 輸入的資料,就必須搭配 <keep-alive><keep-alive>
。
index.html
1 | <!DOCTYPE html> |
12 行
1 | <keep-alive> |
在 <component></component>
外部加上 <keep-alive></keep-alive>
,則 Dynamic Component 內的資料將獲得保留。
JavaScript 的寫法不用改變。
Vue 底層會將 user 的輸入保留,然後切換 component 時,除了建立新的 component 外,還會將 user 原本所輸入的資料也
重新
填回新建立的 component,讓動態切換 component 更方便
Conclusion
- Vue 提供了 Vue Component,讓我們將 HTML 會重複的部分可以使用 component 包起來,方便閱讀,也更方便維護
- MVVM 可以 Component-based 完美結合,但
data
property 必須改用data
function - Component 有時候會違背 Browser 的 DOM Parser,此時必須改變寫法繞過 Browser
- Dynamic Component 讓我們可以根據商業邏輯自行切換 component
- 透過神奇的
<keep-alive></keep-alive>
,user 原本的輸入將保留在 component 內
Sample Code
完整的範例可以在我的 GitHub 上找到