# [Vue.js] 元件Component用法
###### tags: `Vue.js`
### 什麼是元件?
Vue 應用程式的使用,主要是以 Vue component 元件 所組成,而最上層是以 Root 為主,下面包含 Header, Content 與 side (不同的 component )。而每個 Component 當中的 data 都會是互相獨立的,使用前端框架的好處在於,很多時候同樣重複的事情,你只需要做一次,就能重複使用,並且在後面的維護上更為方便。
Props:由外向內傳入,特性為資料一更新,則傳入 data ,隨即更新頁面。
Emit:由內向外傳出,特性為事件觸發才更新資料,屬於事件類型。
原先寫法
```
<body>
<div id="app">
<button @click="count++">You clicked me {{ count }} times.</button>
</div>
</body>
```
```
var app = new Vue({
el: '#app',
data: {
count: 0
}
});
```
載入元件的寫法
```
<body>
<div id="app">
<button-counter></button-counter>
<button-counter></button-counter>
</div>
</body>
```
```
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})
var app = new Vue({
el: '#app'
});
```
## props使用時機
下方html原資料狀態

下方script(這裡使用x-template)
命名row-component當作html的元件名稱

下方是錯誤案例,外部元件的指令沒有溝通管道因此無法更改元件內部的item.name、item.cach、item.icash的值

下方則增加props:[person]提供一個接口讓外部的資料能傳入內部做使用,例如再person="item",item的資料會傳到person裡,再匯到元件內部,提供給person.name做使用,以顯示正確資料。

## x-template使用時機
若是 template 中的語法太過複雜,會使用 x-template 的方式來改善程式的可讀性。 x-template 的宣告必須在另一個 <script> 元素中,並為其帶上 text/x-template 的類型,然後通過一個 id 將模板引用過去。
```
<script type="text/x-template" id="rowCompTemp">
<tr>
<td>{{ person.name }}</td>
<td>{{ person.cash }}</td>
<td>{{ person.icash }}</td>
</tr>
</script>
Vue.component('row-comp', {
props: ['person'],
template: '#rowCompTemp'
})
```
## 元件使用方式(全域、區域註冊)
・Global 全域註冊
如果有元件共用的需求,我們會使用Vue.component 語法來註冊一個元件,在註冊全域元件時要給予兩個參數,分別為「組件名稱」及「選項物件」,在下方範例中「組件名稱」為 component-layout ,「選項物件」則為其後的內容。
```
///// 全域註冊 /////
<div id=”app”>
<component-layout></component-layout>
</div>
<script>
Vue.component("component-layout", {
template: `<div>{{text}}</div>`,
data(){
return{
text:'我是全域註冊'
}
}
});
let vm = new Vue({
el: "#app"
});
<script>
```
不過使用全域註冊的缺點是,不管有沒有使用到這個元件,其元件就一定會載入,因此,使用全域註冊會將原本不需要的組件載入進來,整體而言,會拖慢網頁載入的時間。
・Local 局部註冊
如同前面所提,考量全域註冊的缺點,某些特定元件就會用區域註冊的方式,註冊在需要使用它的元件之中,同時,它是一個選項物件,可以由 components 這個選項物件屬性載入 Vue 實例 使用。
```
///// 局部註冊 /////
<div id=”app”>
<component-layout></component-layout>
</div>
<script>
let vm = new Vue({
el: "#app",
components:{
'component-layout' :{
template: `<div>{{text}}</div>`,
data(){
return {
text:'我是局部註冊'
}
}
}
}
});
<script>
```
## 元件中 data 必須是函式
在 Vue 實例 中,data 可以是 物件(Object) 或 函式(Function),但元件的 data 只能是函式(Function)。
原因是 Vue component 元件 是可以重複利用的,而且在正常的情況下,它會使用不同的資料,因此,data是函式(Function)的話,每次註冊組件,則會回傳一個新的物件。
```
<script>
Vue.component("my-component", {
template: '<div>{{text}}</div>',
data(){
return{
text:'元件中 data 為函式'
}
}
});let vm = new Vue({
el: "#app"
});<script>
```
```
Vue.component(‘counter-component’, {
data: function(){
return {counter: 0}
},
template: ‘#counter-component’} )
```
## is屬性功用
模版的來源有兩種:DOM 模版 (DOM Template) 和 字串模版 (String Template)。在 DOM 模版狀況下,若瀏覽器無法正確渲染 DOM Elements,則 Vue.js 就無法對模版做正確的解析。
component 的:is屬性可動態決定要渲染的模版。is屬性可解決在特定 HTML 標籤包含格式下的問題,像是<table>只能包<tr>而非客製化標籤<my-row>。
```
<div id="app">
<table>
<tr is="example-item"></tr>
</table>
</div>
<script type="text/x-template" id="my_component_with_template">
<tr>
<td>A</td>
<td>B</td>
<td>C</td>
</tr>
</script>
```
```
var vm = new Vue({
el: '#app',
delimiters: ['${', '}'],
components: {
'example-item': {
template: '#my_component_with_template'
}
}
});
```
## camelCase 小駝峰 vs kebab-case
HTML 是不區分大小寫的,而 JavaScript 是嚴格區分大小寫的。因此,若非使用以 JavaScript 產生模版的方式,意即「字串模版 (String Template)」,而是使用 HTML 模版時,屬性名稱必須使用以 dash 分隔的 kebab-case 命名。
例如,在 HTML 中撰寫屬性名稱「user name」如下
(O) <prompt-component :user-name="name"></prompt-component>
(X) <prompt-component :userName="name"></prompt-component>
再次強調,這種狀況只有在 HTML 中樣版會出現,字串模版 (String Template)則無此限制。
## 字面量語法(Literal)vs 動態語法(Dynamic)
由於屬性 id 的值是由 data 的 id 代入,若只是經由屬性傳遞資料,模版不會做任何處理,得到的資料型態是「string」;但若使用 v-bind 屬性綁定(簡寫為:),將來會與 Vue Instance 結合,解析模版會當成 JavaScript 表達式做計算,得到的資料型態是「number」。
Case 1:Literal
這裡的 id 的資料型態是「string」。點擊按鈕後 console 出來的結果是 string。
`<prompt-component id="id"></prompt-component>`
Case 2:Dynamic
這裡的 id 的資料型態是「number」。點擊按鈕後 console 出來的結果是 number。
`<prompt-component :id="id"></prompt-component>`
## emit向外層傳遞事件
還記得剛剛曾說過的「props in, events out」嗎? 雖然我們不能在 <button-counter> 裡面控制父層的 sum,但我們可以透過「事件」來讓父層去觸發資料更新。
要觸發事件,自然要先註冊事件。
第一步,在 view 上面加上 @add-sum="sum++" 來註冊我們的自訂事件「add-sum」,在 add-sum 這個事件被觸發的時候,會對父層的做 sum++。
```
<body>
<div id="app">
<h1>Total: {{sum}}</h1>
<button-counter @add-sum="sum++"></button-counter>
<button-counter @add-sum="sum++"></button-counter>
</div>
</body>
```
那麼,在 button-counter 的部分,則是在原本的 click 事件中,加入 $emit('add-sum') 來表示當使用者點擊按鈕的同時,也要跟著發送 add-sum 事件:
```
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: '<button @click="count++; $emit(\'add-sum\')">You clicked me {{ count }} times.</button>',
})
var app = new Vue({
el: '#app',
data: {
sum: 0
}
});
```
## slot元件插槽
在<no-slot-component>無論輸入任何值都不會替換
```
<h2>沒有插槽可替換的狀態</h2>
<no-slot-component>
<p>替換的文字</p>
</no-slot-component>
```
```
<script type="text/x-template" id="noSlotComponent">
<div class="alert alert-warning">
<h6>我是一個元件</h6>
<p>
這沒有插槽。
</p>
</div>
</script>
```
### 單個插槽(Single Slot)
父元件內的宿主內容會被放入子元件的 <slot> 中,只有當父元件內的宿主內容為空時,才會顯示子元件中 <slot> 內的備用內容。
```
<h2>Slot 基礎範例</h2>
<single-slot-component> <!-- 如果中間有添加字串,元件裡的slot文字則不會出現 -->
<p>使用這段取代原本的 Slot。</p>
</single-slot-component>
<single-slot-component> <!-- 如果中間無添加的字串,則會顯示slot裡的文字 -->
</single-slot-component>
```
```
<script type="text/x-template" id="singleSlotComponent">
<div class="alert alert-warning">
<h6>我是一個元件</h6>
<slot>
如果沒有內容,則會顯示此段落。
</slot>
</div>
</script>
```
### 具名插槽(Named Slots)
使用屬性 name 決定配置的內容。沒有 name 的匿名插槽將成為預設插槽,匹配不到的內容會放置在此;若沒有默認插槽,匹配不到的內容會被捨棄。
```
<h2>具名插槽</h2>
<named-slot-component>
</named-slot-component>
<named-slot-component>
<header slot= "header">替換的 Header</header>
<template slot="btn">按鈕內容</template>
</named-slot-component>
```
```
<script type="text/x-template" id="namedSlotComponent">
<div class="card-header">
<slot name="header">這段是預設的文字</slot>
</div>
<div class="card-body">
<a href="#" class="btn btn-primary">
<slot name="btn">spanGo somewhere</slot>
</a>
</div>
</script>
```