
*****
## 自定義組件
有時候有一組html結構的代碼,并且這個上面可能還綁定了事件。然后這段代碼可能有多個地方都被使用到了,如果都是拷貝來拷貝去,很多代碼都是重復的,包括事件部分的代碼都是重復的。那么這時候我們就可以把這些代碼封裝成一個組件,以后在使用的時候就跟使用普通的html元素一樣,拿過來用就可以了。
### 基本使用
```
<div id="app">
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
</div>
<script>
Vue.component('button-counter', {
template: '<button v-on:click="count+=1">點擊了{{ count }}次</button>'
data: function(){
return {
count: 0
}
},
});
let vm = new Vue({
el: "#app",
data: {}
});
</script>
```
以上我們創建了一個叫做button-counter的組件,這個組件實現了能夠記錄點擊了多少次按鈕的功能。后期如果我們想要使用,就直接通過button-counter使用就可以了。然后因為組件是可復用的Vue實例,所以它們與new Vue接收相同的選項,例如data、computed、watch、methods以及生命周期鉤子等。僅有的例外是像el這樣根實例特有的選項。另外需要注意的是:組件中的data必須為一個函數!
### 給組件添加屬性
像原始的html元素都有自己的一些屬性,而我們自己創建的組件,也可以通過prop來添加自己的屬性。這樣別人在使用你創建的組件的時候就可以傳遞不同的參數了
```
<div id="app">
<blog-post v-for="blog in blogs" :title="blog.title"></blog-post>
<blog-post v-bind:blogs="blogs"></blog-post>
</div>
<script>
Vue.component('blog-post', {
props: ['blogs'],
template: `
<table>
<tr>
<th>序號</th>
<th>標題</th>
</tr>
<tr v-for="(blog,index) in blogs">
<td>{{index+1}}</td>
<td>{{blog.title}}</td>
</tr>
</table>
`
})
new Vue({
el: "#app",
data: {
blogs: [
{"title":"鋼鐵是怎樣練成的?","id":1},
{"title":"AI會毀滅人類嗎?","id":2},
{"title":"如何學好Vue!","id":3},
]
}
});
</script>
```
### 單一根元素
如果自定義的組件中,會出現很多html元素,那么根元素必須只能有一個,其余的元素必須包含在這個根元素中。比如以下是一個組件中的代碼,會報錯:
```
<h3>{{ title }}</h3>
<div v-html="content"></div>
```
我們應該改成:
```
<div class="blog-post">
<h3>{{ title }}</h3>
<div v-html="content"></div>
</div>
```
### 子組件事件和傳遞事件到父組件
子組件中添加事件跟之前的方式是一樣的,然后如果發生某個事件后想要通知父組件,那么可以使用this.$emit函數來實現。
```
<div id="app">
<blog-item v-for="blog in blogs" v-bind:blog="blog" @check-changed="checks"></blog-item>
<div v-for="blog in componentblog">
{{blog.title}}
</div>
</div>
<script>
Vue.component('blog-item',{
props:['blog'],
template:`
<div>
<span>{{blog.title}}</span>
<input type="checkbox" @click="onCheck">
</div>
`,
methods:{
onCheck:function(){
// console.log(123)
this.$emit('check-changed',this.blog)
}
}
})
new Vue({
el: '#app',
data: {
blogs:[
{"title":"鋼鐵是怎樣練成的?","id":1},
{"title":"AI會毀滅人類嗎?","id":2},
{"title":"如何學好Vue!","id":3},
],
componentblog:[]
},
methods:{
checks:function(blog){
// indexOf 判斷某個元素在數組中的位置,返回下標
var index = this.componentblog.indexOf(blog)
if(index >= 0){
this.componentblog.splice(index,1)
}else{
this.componentblog.push(blog)
}
console.log(blog)
}
}
})
</script>
```
需要注意的是,因為html中大小寫是不敏感的,所以在定義子組件傳給父組件事件名稱的時候,不要使用myEvent這種駝峰命名法,而是使用my-event這種規則。
### 自定義組件v-model
一個組件上的v-model默認會利用名為value的prop(屬性)和名為input的事件,但是像單選框、復選框等類型的輸入控件可能會將value特性用于不同的目的。這時候我們可以在定義組件的時候,通過設置model選項可以用來實現不同的處理方式
```
<div id="app">
<stepper v-model:value="goods_count"></stepper>
</div>
<script>
Vue.component('stepper',{
props:['count'],
model:{
event: 'count-changed',
prop: "count"
},
template:`
<div>
<button @click="sub">-</button>
<span>{{count}}</span>
<button @click="add">+</button>
</div>
`,
methods:{
sub:function(){
this.$emit("count-changed", this.count-1)
},
add:function(){
this.$emit("count-changed", this.count+1)
}
}
});
new Vue({
el: "#app",
data:{
"goods_count":0
}
})
</script>
```
其中的props定義的屬性分別是給外面調用組件的時候使用的。model中定義的prop:'count'是告訴后面使用v-model的時候,要修改哪個屬性;event:'count-changed'是告訴v-model,后面觸發哪個事件的時候要修改屬性。
### 插槽
我們定義完一個組件后,可能在使用的時候還需要往這個組件中插入新的元素或者文本。這時候就可以使用插槽來實現。
```
<div id="app">
<navigation-link url="/profile/">
個人中心
</navigation-link>
</div>
<script>
Vue.component('navigation-link', {
props: ['url'],
template: `
<a v-bind:href="url" class="nav-link">
<slot></slot>
</a>
`
})
new Vue({
el: "#app"
});
</script>
```
當組件渲染的時候,<slot></slot>將會被替換為“個人中心”。插槽內可以包含任何模板代碼,包括HTML:
```
<navigation-link url="/profile">
<!-- 添加一個 Font Awesome 圖標 -->
<span class="fa fa-user"></span>
個人中心
</navigation-link>
```
如果<navigation-link>沒有包含一個<slot>元素,則該組件起始標簽和結束標簽之間的任何內容都會被拋棄。