組件是可復(fù)用的 Vue 實(shí)例弄抬,且?guī)в幸粋€名字:在這個例子中是 <button-counter>疆拘。我們可以在一個通過 new Vue 創(chuàng)建的 Vue 根實(shí)例中蜕猫,把這個組件作為自定義元素來使用:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>組件基礎(chǔ)</title>
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
</head>
<div id="components-demo">
<!-- 通過 new Vue 創(chuàng)建的 Vue 根實(shí)例中,把這個組件作為自定義元素來使用 -->
<!--組件的復(fù)用-->
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
</div>
<!-- 注意當(dāng)點(diǎn)擊按鈕時哎迄,每個組件都會各自獨(dú)立維護(hù)它的 count回右。因?yàn)槟忝坑靡淮谓M件,就會有一個它的新實(shí)例被創(chuàng)建漱挚。 -->
<body>
</body>
<script type="text/javascript" src="js/vue.js"></script>
<script>
// 組件是可復(fù)用的 Vue 實(shí)例翔烁,定義一個名為 button-counter 的新組件
Vue.component('button-counter',{
//一個組件的 data 選項必須是一個函數(shù)
//因此每個實(shí)例可以維護(hù)一份被返回對象的獨(dú)立的拷貝
data: function(){
return {
count: 0
}
},
template: `
<button @click="count++">您點(diǎn)擊了{(lán){count}}次</button>
`
})
new Vue({
el: '#components-demo'
})
</script>
</html>
組件能夠復(fù)用的原因:
每個組件都會各自獨(dú)立維護(hù)它的 count。因?yàn)槟忝坑靡淮谓M件旨涝,就會有一個它的新實(shí)例被創(chuàng)建蹬屹。
注意:一個組件的 data 選項必須是一個函數(shù),因此每個實(shí)例可以維護(hù)一份被返回對象的獨(dú)立的拷貝
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>prop</title>
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
</head>
<div id="components-prop">
<blog-post v-for="post in posts" v-bind:post="post"></blog-post>
</div>
<body>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script type="text/javascript">
// 注冊組件白华,傳遞prop屬性
Vue.component('blog-post', {
template: "<p>{{post.id}}.{{post.title}}</p>",
props: ['post'],
})
new Vue({
el: '#components-prop',
data: {
posts: [
{ id: 1, title: 'My journey with Vue' },
{ id: 2, title: 'Blogging with Vue' },
{ id: 3, title: 'Why Vue is so fun' }
]
}
})
</script>
</html>
如上所示慨默,你會發(fā)現(xiàn)我們可以使用 v-bind
來動態(tài)傳遞 prop。這在你一開始不清楚要渲染的具體內(nèi)容弧腥,比如從一個 API 獲取博文列表的時候厦取,是非常有用的。
單個根元素
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>prop</title>
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
</head>
<div id="components-prop">
<blog-post v-for="post in posts" v-bind:key="post.id" v-bind:post="post"></blog-post>
</div>
<body>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script type="text/javascript">
// 注冊組件鸟赫,傳遞prop屬性
Vue.component('blog-post', {
props: ['post'],
// 每個組件必須只有一個根元素蒜胖,這里外面必須套一層div,不然模板div里面的內(nèi)容不會顯示
template: `
<div class="blog-post">
<h3 v-text="post.title"></h3>
<div v-html="post.id + '.' + post.content"></div>
</div>
`,
})
new Vue({
el: '#components-prop',
data: {
posts: [
{ id: 1, title: 'My journey with Vue',content: 'My journey with Vue抛蚤,My journey with Vue' },
{ id: 2, title: 'Blogging with Vue',content: 'Blogging with Vue,Blogging with Vue' },
{ id: 3, title: 'Why Vue is so fun',content: 'Why Vue is so funWhy Vue is so fun' }
]
}
})
</script>
</html>
監(jiān)聽子組件事件
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>prop</title>
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
</head>
<div id="components-prop" :style="{ fontSize: postFontSize + 'em' }">
<!-- 父級組件通過 v-on 監(jiān)聽子組件實(shí)例的任意自定義事件台谢,這里是監(jiān)聽子組件的change-text自定義事件 -->
<blog-post v-for="post in posts" v-bind:key="post.id" v-bind:post="post" v-on:change-text="postFontSize+=0.1"></blog-post>
</div>
<body>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script type="text/javascript">
// 注冊組件,傳遞prop屬性
Vue.component('blog-post', {
props: ['post'],
// 每個組件必須只有一個根元素岁经,這里外面必須套一層div朋沮,不然模板div里面的內(nèi)容不會顯示
// 同時子組件可以通過調(diào)用內(nèi)建的 $emit 方法 并傳入事件名稱(這里是change-text)來觸發(fā)這個事件
template: `
<div class="blog-post">
<h3 v-text="post.title"></h3>
<button v-on:click="$emit('change-text')">字體變大</button>
<div v-html="post.id + '.' + post.content"></div>
</div>
`,
})
new Vue({
el: '#components-prop',
data: {
posts: [
{ id: 1, title: 'My journey with Vue',content: 'My journey with Vue,My journey with Vue' },
{ id: 2, title: 'Blogging with Vue',content: 'Blogging with Vue,Blogging with Vue' },
{ id: 3, title: 'Why Vue is so fun',content: 'Why Vue is so funWhy Vue is so fun' }
],
// 在其父組件中缀壤,我們可以通過添加一個 postFontSize 數(shù)據(jù)屬性來支持這個功能
postFontSize: 1
}
})
</script>
</html>
當(dāng)點(diǎn)擊這個按鈕時樊拓,我們需要告訴父級組件放大所有博文的文本。幸好 Vue 實(shí)例提供了一個自定義事件的系統(tǒng)來解決這個問題塘慕。父級組件可以像處理 native DOM 事件一樣通過 v-on 監(jiān)聽子組件實(shí)例的任意事件:同時子組件可以通過調(diào)用內(nèi)建的 $emit
方法 并傳入事件名稱來觸發(fā)一個事件:
有了這個 v-on:enlarge-text="postFontSize += 0.1" 監(jiān)聽器筋夏,父級組件就會接收該事件并更新 postFontSize 的值。
使用事件拋出一個值
有的時候用一個事件來拋出一個特定的值是非常有用的图呢。例如我們可能想讓 <blog-post> 組件決定它的文本要放大多少条篷。這時可以使用 $emit 的第二個參數(shù)來提供這個值:
<button v-on:click="$emit('change-text',0.1)">字體變大</button>
然后當(dāng)在父級組件監(jiān)聽這個事件的時候,我們可以通過 $event 訪問到被拋出的這個值:
<blog-post v-for="post in posts" v-bind:key="post.id" v-bind:post="post" v-on:change-text="postFontSize+=$event"></blog-post>
完整demo如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>prop</title>
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
</head>
<div id="components-prop" :style="{ fontSize: postFontSize + 'em' }">
<!-- 父級組件通過 v-on 監(jiān)聽子組件實(shí)例的任意自定義事件蛤织,這里是監(jiān)聽子組件的change-text自定義事件 -->
<blog-post v-for="post in posts" v-bind:key="post.id" v-bind:post="post" v-on:change-text="postFontSize+=$event"></blog-post>
</div>
<body>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script type="text/javascript">
// 注冊組件赴叹,傳遞prop屬性
Vue.component('blog-post', {
props: ['post'],
// 每個組件必須只有一個根元素,這里外面必須套一層div指蚜,不然模板div里面的內(nèi)容不會顯示
// 同時子組件可以通過調(diào)用內(nèi)建的 $emit 方法 并傳入事件名稱(這里是change-text)來觸發(fā)這個事件
template: `
<div class="blog-post">
<h3 v-text="post.title"></h3>
<button v-on:click="$emit('change-text',0.1)">字體變大</button>
<div v-html="post.id + '.' + post.content"></div>
</div>
`,
})
new Vue({
el: '#components-prop',
data: {
posts: [
{ id: 1, title: 'My journey with Vue',content: 'My journey with Vue乞巧,My journey with Vue' },
{ id: 2, title: 'Blogging with Vue',content: 'Blogging with Vue,Blogging with Vue' },
{ id: 3, title: 'Why Vue is so fun',content: 'Why Vue is so funWhy Vue is so fun' }
],
// 在其父組件中,我們可以通過添加一個 postFontSize 數(shù)據(jù)屬性來支持這個功能
postFontSize: 1
}
})
</script>
</html>
或者摊鸡,如果這個事件處理函數(shù)是一個方法:那么這個值將會作為第一個參數(shù)傳入這個方法
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>prop</title>
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
</head>
<div id="components-prop" :style="{ fontSize: postFontSize + 'em' }">
<!-- 父級組件通過 v-on 監(jiān)聽子組件實(shí)例的任意自定義事件绽媒,這里是監(jiān)聽子組件的change-text自定義事件 -->
<blog-post v-for="post in posts" v-bind:key="post.id" v-bind:post="post" v-on:change-text="onChangeText"></blog-post>
</div>
<body>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script type="text/javascript">
// 注冊組件,傳遞prop屬性
Vue.component('blog-post', {
props: ['post'],
// 每個組件必須只有一個根元素柱宦,這里外面必須套一層div些椒,不然模板div里面的內(nèi)容不會顯示
template: `
<div class="blog-post">
<h3 v-text="post.title"></h3>
<button v-on:click="$emit('change-text',0.1)">字體變大</button>
<div v-html="post.id + '.' + post.content"></div>
</div>
`,
})
new Vue({
el: '#components-prop',
methods: {
onChangeText: function (enlargeAmount) {
this.postFontSize += enlargeAmount
}
},
data: {
posts: [
{ id: 1, title: 'My journey with Vue',content: 'My journey with Vue,My journey with Vue' },
{ id: 2, title: 'Blogging with Vue',content: 'Blogging with Vue,Blogging with Vue' },
{ id: 3, title: 'Why Vue is so fun',content: 'Why Vue is so funWhy Vue is so fun' }
],
// 在其父組件中掸刊,我們可以通過添加一個 postFontSize 數(shù)據(jù)屬性來支持這個功能
postFontSize: 1
}
})
</script>
</html>
在組件上使用 v-model
v-model本身就是一個利用叫value的prop和叫input的自定義事件免糕。所以完全可以自己寫,因?yàn)閱芜x忧侧,復(fù)選等類型的輸入控鍵石窑,會使用value特性用于其他目的。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>在組件上模擬v-model</title>
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
</head>
<div id="app">
<custom-input v-bind:value="searchText" v-on:input="searchText=$event"></custom-input>
<p>{{ searchText}}</p>
</div>
<!--
1. v-bind:value='searchText'
綁定: 初始化data的數(shù)據(jù)對象和Prop特性value
數(shù)據(jù)流動:從外到內(nèi)蚓炬。外部傳入的數(shù)據(jù)可以被Vue實(shí)例得到松逊,存在自定義的Props: value特性中。
2. v-on:input自定義='searchText = $event '
監(jiān)聽: input事件肯夏。自定義的事件经宏。非原生HTMLDOM-input犀暑。
數(shù)據(jù)流動:從內(nèi)到外。Vue實(shí)例的value特性的值烁兰,被賦予數(shù)據(jù)對象searchText耐亏。
-->
<body>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script type="text/javascript">
// 注冊組件,傳遞prop屬性
Vue.component('custom-input', {
props: ['value'],
template: `
<input v-bind:value="value" v-on:input="$emit('input', $event.target.value)">
`
})
new Vue({
el: '#app',
data: {
searchText: "模擬v-model"
},
})
/*
1. <input>中的v-bind:value="value"
綁定: input元素的value特性和組件的prop特性value
數(shù)據(jù)流動:從外到內(nèi)沪斟。外部傳入的數(shù)據(jù)被prop value特性得到,然后再被Vue實(shí)例的<input>的value特性得到广辰。
2. <input>中的v-on:input="$emit('input', $event.target.value)"
監(jiān)聽: input事件。這是原生的HTMLDOM事件主之。
數(shù)據(jù)流動:從內(nèi)到外择吊。當(dāng)用戶在輸入框輸入任意字符后,后臺監(jiān)聽到input事件槽奕,然后執(zhí)行$emit這個實(shí)例方法几睛。
$emit(eventName, 參數(shù)),這個實(shí)例方法會觸發(fā)當(dāng)前實(shí)例上的事件'input'粤攒,并發(fā)送參數(shù)枉长。
實(shí)例上的v-on監(jiān)聽到了事件'input',執(zhí)行一個內(nèi)聯(lián)語句琼讽。參數(shù)被賦予searchText對象必峰。
*/
</script>
</html>
關(guān)于event是觸發(fā)的事件,這里是在輸入框輸入的動作钻蹬。
$event.target是這個動作作用在哪個元素上吼蚁。target特性返回被事件激活的元素。這里是輸入框input元素问欠。
value指的是input中的value特性肝匆,即輸入的內(nèi)容。
通過插槽分發(fā)內(nèi)容
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="app">
<alert-box>
Something bad happened.
</alert-box>
</div>
<script src="js/vue.js"></script>
<script>
Vue.component('alert-box', {
template: `
<div class="demo-alert-box">
<strong>Error!</strong>
<slot></slot>
</div>
`
});
var app = new Vue({
el: '#app'
})
</script>
</body>
</html>
動態(tài)組件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>動態(tài)組件Tab</title>
<style>
.tab-button {padding: 6px 10px; border-top-left-radius: 3px; border-top-right-radius: 3px; border: 1px solid #ccc; cursor: pointer; background: #f0f0f0; margin-bottom: -1px; margin-right: -1px; }
.tab-button:hover {background: #e0e0e0; }
.tab-button.active {background: #e0e0e0; }
.tab {border: 1px solid #ccc; padding: 10px; }
</style>
</head>
<body>
<div id="dynamic-component-demo" class="demo">
<button v-for="tab in tabs" v-bind:key="tab" v-bind:class="['tab-button', { active: currentTab === tab }]" v-on:click="currentTab = tab">
{{ tab }}
</button>
<component v-bind:is="currentTabComponent" class="tab"></component>
</div>
<script src="js/vue.js"></script>
<script>
Vue.component("tab-home", {template: "<div>Home component</div>"});
Vue.component("tab-posts", {template: "<div>Posts component</div>"});
Vue.component("tab-archive", {template: "<div>Archive component</div>"});
new Vue({
el: "#dynamic-component-demo",
data: {
currentTab: "Home",
tabs: ["Home", "Posts", "Archive"]
},
computed: {
currentTabComponent: function() {
return "tab-" + this.currentTab.toLowerCase();
}
}
});
</script>
</body>
</html>
上述內(nèi)容可以通過 Vue 的 <component> 元素加一個特殊的 is 特性來實(shí)現(xiàn):
解析 DOM 模板時的注意事項
有些 HTML 元素顺献,諸如 <ul>旗国、<ol>、<table> 和 <select>注整,對于哪些元素可以出現(xiàn)在其內(nèi)部是有嚴(yán)格限制的能曾。而有些元素,諸如 <li>肿轨、<tr> 和 <option>寿冕,只能出現(xiàn)在其它某些特定的元素內(nèi)部。
這會導(dǎo)致我們使用這些有約束條件的元素時遇到一些問題椒袍。例如:
<table>
<blog-post-row></blog-post-row>
</table>
這個自定義組件 <blog-post-row> 會被作為無效的內(nèi)容提升到外部驼唱,并導(dǎo)致最終渲染結(jié)果出錯。幸好這個特殊的 is 特性給了我們一個變通的辦法:
<table>
<tr is="blog-post-row"></tr>
</table>