組件允許我們将 UI 劃分為(wèi)獨立的、可(kě)重用的部分,并且可(kě)以對每個部分進行(xíng)單獨的思考。在實際應用中,組件常常被組織成層層嵌套的樹(shù)狀結構:
這(zhè)和(hé)我們嵌套 HTML 元素的方式類似,Vue 實現了(le)自(zì)己的組件模型,使我們可(kě)以在每個組件內(nèi)封裝自(zì)定義內(nèi)容與邏輯。Vue 同樣也能(néng)很(hěn)好地(dì)配合原生 Web Component。如(rú)果你想知道(dào) Vue 組件與原生 Web Components 之間(jiān)的關系,可(kě)以閱讀此章(zhāng)節。
定義一(yī)個組件
當使用構建步驟時(shí),我們一(yī)般會将 Vue 組件定義在一(yī)個單獨的 .vue 文件中,這(zhè)被叫做(zuò)單文件組件 (簡稱 SFC):
vue
<script>
export default {
data() {
return {
count: 0
}
}
}
</script>
<template>
<button @click="count++">You clicked me {{ count }} times.</button>
</template>
當不使用構建步驟時(shí),一(yī)個 Vue 組件以一(yī)個包含 Vue 特定選項的 JavaScript 對象來定義:
js
export default {
data() {
return {
count: 0
}
},
template: `
<button @click="count++">
You clicked me {{ count }} times.
</button>`
}
這(zhè)裏的模闆是一(yī)個內(nèi)聯的 JavaScript 字符串,Vue 将會在運行(xíng)時(shí)編譯它。你也可(kě)以使用 ID 選擇器來指向一(yī)個元素 (通(tōng)常是原生的 <template> 元素),Vue 将會使用其內(nèi)容作(zuò)為(wèi)模闆來源。
上(shàng)面的例子中定義了(le)一(yī)個組件,并在一(yī)個 .js 文件裏默認導出了(le)它自(zì)己,但(dàn)你也可(kě)以通(tōng)過具名導出在一(yī)個文件中導出多個組件。
使用組件
TIP
我們會在接下(xià)來的指引中使用 SFC 語法,無論你是否使用構建步驟,組件相關的概念都(dōu)是相同的。示例一(yī)節中展示了(le)兩種場景中的組件使用情況。
要(yào)使用一(yī)個子組件,我們需要(yào)在父組件中導入它。假設我們把計數(shù)器組件放在了(le)一(yī)個叫做(zuò) ButtonCounter.vue 的文件中,這(zhè)個組件将會以默認導出的形式被暴露給外(wài)部。
vue
<script>
import ButtonCounter from './ButtonCounter.vue'
export default {
components: {
ButtonCounter
}
}
</script>
<template>
<h1>Here is a child component!</h1>
<ButtonCounter />
</template>
若要(yào)将導入的組件暴露給模闆,我們需要(yào)在 components 選項上(shàng)注冊它。這(zhè)個組件将會以其注冊時(shí)的名字作(zuò)為(wèi)模闆中的标簽名。
當然,你也可(kě)以全局地(dì)注冊一(yī)個組件,使得它在當前應用中的任何組件上(shàng)都(dōu)可(kě)以使用,而不需要(yào)額外(wài)再導入。關于組件的全局注冊和(hé)局部注冊兩種方式的利弊,我們放在了(le)組件注冊這(zhè)一(yī)章(zhāng)節中專門讨論。
組件可(kě)以被重用任意多次:
template
<h1>Here is a child component!</h1>
<ButtonCounter />
<ButtonCounter />
<ButtonCounter />
你會注意到,每當點擊這(zhè)些按鈕時(shí),每一(yī)個組件都(dōu)維護着自(zì)己的狀态,是不同的 count。這(zhè)是因為(wèi)每當你使用一(yī)個組件,就創建了(le)一(yī)個新的實例。
在單文件組件中,推薦為(wèi)子組件使用 PascalCase 的标簽名,以此來和(hé)原生的 HTML 元素作(zuò)區(qū)分。雖然原生 HTML 标簽名是不區(qū)分大小寫的,但(dàn) Vue 單文件組件是可(kě)以在編譯中區(qū)分大小寫的。我們也可(kě)以使用 /> 來關閉一(yī)個标簽。
如(rú)果你是直接在 DOM 中書(shū)寫模闆 (例如(rú)原生 <template> 元素的內(nèi)容),模闆的編譯需要(yào)遵從(cóng)浏覽器中 HTML 的解析行(xíng)為(wèi)。在這(zhè)種情況下(xià),你應該需要(yào)使用 kebab-case 形式并顯式地(dì)關閉這(zhè)些組件的标簽。
template
<!-- 如(rú)果是在 DOM 中書(shū)寫該模闆 -->
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
請(qǐng)看(kàn) DOM 模闆解析注意事項了(le)解更多細節。
傳遞 props
如(rú)果我們正在構建一(yī)個博客,我們可(kě)能(néng)需要(yào)一(yī)個表示博客文章(zhāng)的組件。我們希望所有的博客文章(zhāng)分享相同的視(shì)覺布局,但(dàn)有不同的內(nèi)容。要(yào)實現這(zhè)樣的效果自(zì)然必須向組件中傳遞數(shù)據,例如(rú)每篇文章(zhāng)标題和(hé)內(nèi)容,這(zhè)就會使用到 props。
Props 是一(yī)種特别的 attributes,你可(kě)以在組件上(shàng)聲明(míng)注冊。要(yào)傳遞給博客文章(zhāng)組件一(yī)個标題,我們必須在組件的 props 列表上(shàng)聲明(míng)它。這(zhè)裏要(yào)用到 props 選項:
vue
<!-- BlogPost.vue -->
<script>
export default {
props: ['title']
}
</script>
<template>
<h4>{{ title }}</h4>
</template>
當一(yī)個值被傳遞給 prop 時(shí),它将成為(wèi)該組件實例上(shàng)的一(yī)個屬性。該屬性的值可(kě)以像其他(tā)組件屬性一(yī)樣,在模闆和(hé)組件的 this 上(shàng)下(xià)文中訪問(wèn)。
一(yī)個組件可(kě)以有任意多的 props,默認情況下(xià),所有 prop 都(dōu)接受任意類型的值。
當一(yī)個 prop 被注冊後,可(kě)以像這(zhè)樣以自(zì)定義 attribute 的形式傳遞數(shù)據給它:
template
<BlogPost title="My journey with Vue" />
<BlogPost title="Blogging with Vue" />
<BlogPost title="Why Vue is so fun" />
在實際應用中,我們可(kě)能(néng)在父組件中會有如(rú)下(xià)的一(yī)個博客文章(zhāng)數(shù)組:
js
export default {
// ...
data() {
return {
posts: [
{ id: 1, title: 'My journey with Vue' },
{ id: 2, title: 'Blogging with Vue' },
{ id: 3, title: 'Why Vue is so fun' }
]
}
}
}
這(zhè)種情況下(xià),我們可(kě)以使用 v-for 來渲染它們:
template
<BlogPost
v-for="post in posts"
:key="post.id"
:title="post.title"
/>
留意我們是如(rú)何使用 v-bind 來傳遞動态 prop 值的。當事先不知道(dào)要(yào)渲染的确切內(nèi)容時(shí),這(zhè)一(yī)點特别有用。
以上(shàng)就是目前你需要(yào)了(le)解的關于 props 的全部了(le)。如(rú)果你看(kàn)完本章(zhāng)節後還想知道(dào)更多細節,我們推薦你深入閱讀關于 props 的完整指引。
監聽事件
讓我們繼續關注我們的 <BlogPost> 組件。我們會發現有時(shí)候它需要(yào)與父組件進行(xíng)交互。例如(rú),要(yào)在此處實現 A11y 的需求,将博客文章(zhāng)的文字能(néng)夠放大,而頁面的其餘部分仍使用默認字号。
在父組件中,我們可(kě)以添加一(yī)個 postFontSize 數(shù)據屬性來實現這(zhè)個效果:
js
data() {
return {
posts: [
/* ... */
],
postFontSize: 1
}
}
在模闆中用它來控制所有博客文章(zhāng)的字體大小:
template
<div :style="{ fontSize: postFontSize + 'em' }">
<BlogPost
v-for="post in posts"
:key="post.id"
:title="post.title"
/>
</div>
然後,給 <BlogPost> 組件添加一(yī)個按鈕:
vue
<!-- BlogPost.vue, 省略了(le) <script> -->
<template>
<div class="blog-post">
<h4>{{ title }}</h4>
<button>Enlarge text</button>
</div>
</template>
這(zhè)個按鈕目前還沒有做(zuò)任何事情,我們想要(yào)點擊這(zhè)個按鈕來告訴父組件它應該放大所有博客文章(zhāng)的文字。要(yào)解決這(zhè)個問(wèn)題,組件實例提供了(le)一(yī)個自(zì)定義事件系統。父組件可(kě)以通(tōng)過 v-on 或 @ 來選擇性地(dì)監聽子組件上(shàng)抛的事件,就像監聽原生 DOM 事件那(nà)樣:
template
<BlogPost
...
@enlarge-text="postFontSize += 0.1"
/>
子組件可(kě)以通(tōng)過調用內(nèi)置的 $emit 方法,通(tōng)過傳入事件名稱來抛出一(yī)個事件:
<!-- BlogPost.vue, 省略了(le) <script> -->
<template>
<div class="blog-post">
<h4>{{ title }}</h4>
<button @click="$emit('enlarge-text')">Enlarge text</button>
</div>
</template>
因為(wèi)有了(le) @enlarge-text="postFontSize += 0.1" 的監聽,父組件會接收這(zhè)一(yī)事件,從(cóng)而更新 postFontSize 的值。
我們可(kě)以通(tōng)過 emits 選項來聲明(míng)需要(yào)抛出的事件:
vue
<!-- BlogPost.vue -->
<script>
export default {
props: ['title'],
emits: ['enlarge-text']
}
</script>
這(zhè)聲明(míng)了(le)一(yī)個組件可(kě)能(néng)觸發的所有事件,還可(kě)以對事件的參數(shù)進行(xíng)驗證。同時(shí),這(zhè)還可(kě)以讓 Vue 避免将它們作(zuò)為(wèi)原生事件監聽器隐式地(dì)應用于子組件的根元素。
以上(shàng)就是目前你需要(yào)了(le)解的關于組件自(zì)定義事件的所有知識了(le)。如(rú)果你看(kàn)完本章(zhāng)節後還想知道(dào)更多細節,請(qǐng)深入閱讀組件事件章(zhāng)節。
通(tōng)過插槽來分配內(nèi)容
一(yī)些情況下(xià)我們會希望能(néng)和(hé) HTML 元素一(yī)樣向組件中傳遞內(nèi)容:
template
<AlertBox>
Something bad happened.
</AlertBox>
我們期望能(néng)渲染成這(zhè)樣:
This is an Error for Demo Purposes
Something bad happened.
這(zhè)可(kě)以通(tōng)過 Vue 的自(zì)定義 <slot> 元素來實現:
vue
<template>
<div class="alert-box">
<strong>This is an Error for Demo Purposes</strong>
<slot />
</div>
</template>
<style scoped>
.alert-box {
/* ... */
}
</style>
如(rú)上(shàng)所示,我們使用 <slot> 作(zuò)為(wèi)一(yī)個占位符,父組件傳遞進來的內(nèi)容就會渲染在這(zhè)裏。
以上(shàng)就是目前你需要(yào)了(le)解的關于插槽的所有知識了(le)。如(rú)果你看(kàn)完本章(zhāng)節後還想知道(dào)更多細節,請(qǐng)深入閱讀組件插槽章(zhāng)節。
動态組件
有些場景會需要(yào)在兩個組件間(jiān)來回切換,比如(rú) Tab 界面:
上(shàng)面的例子是通(tōng)過 Vue 的 <component> 元素和(hé)特殊的 is attribute 實現的:
template
<!-- currentTab 改變時(shí)組件也改變 -->
<component :is="currentTab"></component>
在上(shàng)面的例子中,被傳給 :is 的值可(kě)以是以下(xià)幾種:
被注冊的組件名
導入的組件對象
你也可(kě)以使用 is attribute 來創建一(yī)般的 HTML 元素。
當使用 <component :is="..."> 來在多個組件間(jiān)作(zuò)切換時(shí),被切換掉的組件會被卸載。我們可(kě)以通(tōng)過 <KeepAlive> 組件強制被切換掉的組件仍然保持“存活”的狀态。
DOM 模闆解析注意事項
如(rú)果你想在 DOM 中直接書(shū)寫 Vue 模闆,Vue 則必須從(cóng) DOM 中獲取模闆字符串。由于浏覽器的原生 HTML 解析行(xíng)為(wèi)限制,有一(yī)些需要(yào)注意的事項。
TIP
請(qǐng)注意下(xià)面讨論隻适用于直接在 DOM 中編寫模闆的情況。如(rú)果你使用來自(zì)以下(xià)來源的字符串模闆,就不需要(yào)顧慮這(zhè)些限制了(le):
單文件組件
內(nèi)聯模闆字符串 (例如(rú) template: '...')
<script type="text/x-template">
大小寫區(qū)分
HTML 标簽和(hé)屬性名稱是不分大小寫的,所以浏覽器會把任何大寫的字符解釋為(wèi)小寫。這(zhè)意味着當你使用 DOM 內(nèi)的模闆時(shí),無論是 PascalCase 形式的組件名稱、camelCase 形式的 prop 名稱還是 v-on 的事件名稱,都(dōu)需要(yào)轉換為(wèi)相應等價的 kebab-case (短橫線連字符) 形式:
js
// JavaScript 中的 camelCase
const BlogPost = {
props: ['postTitle'],
emits: ['updatePost'],
template: `
<h3>{{ postTitle }}</h3>
`
}
template
<!-- HTML 中的 kebab-case -->
<blog-post post-title="hello!" @update-post="onUpdatePost"></blog-post>
閉合标簽
我們在上(shàng)面的例子中已經使用過了(le)閉合标簽 (self-closing tag):
template
<MyComponent />
這(zhè)是因為(wèi) Vue 的模闆解析器支持任意标簽使用 /> 作(zuò)為(wèi)标簽關閉的标志。
然而在 DOM 模闆中,我們必須顯式地(dì)寫出關閉标簽:
template
<my-component></my-component>
這(zhè)是由于 HTML 隻允許一(yī)小部分特殊的元素省略其關閉标簽,最常見(jiàn)的就是 <input> 和(hé) <img>。對于其他(tā)的元素來說,如(rú)果你省略了(le)關閉标簽,原生的 HTML 解析器會認為(wèi)開(kāi)啓的标簽永遠沒有結束,用下(xià)面這(zhè)個代碼片段舉例來說:
template
<my-component /> <!-- 我們想要(yào)在這(zhè)裏關閉标簽... -->
<span>hello</span>
将被解析為(wèi):
template
<my-component>
<span>hello</span>
</my-component> <!-- 但(dàn)浏覽器會在這(zhè)裏關閉标簽 -->
元素位置限制
某些 HTML 元素對于放在其中的元素類型有限制,例如(rú) <ul>,<ol>,<table> 和(hé) <select>,相應的,某些元素僅在放置于特定元素中時(shí)才會顯示,例如(rú) <li>,<tr> 和(hé) <option>。
這(zhè)将導緻在使用帶有此類限制元素的組件時(shí)出現問(wèn)題。例如(rú):
template
<table>
<blog-post-row></blog-post-row>
</table>
自(zì)定義的組件 <blog-post-row> 将作(zuò)為(wèi)無效的內(nèi)容被忽略,因而在最終呈現的輸出中造成錯誤。我們可(kě)以使用特殊的 is attribute 作(zuò)為(wèi)一(yī)種解決方案:
template
<table>
<tr is="vue:blog-post-row"></tr>
</table>
TIP
當使用在原生 HTML 元素上(shàng)時(shí),is 的值必須加上(shàng)前綴 vue: 才可(kě)以被解析為(wèi)一(yī)個 Vue 組件。這(zhè)一(yī)點是必要(yào)的,為(wèi)了(le)避免和(hé)原生的自(zì)定義內(nèi)置元素相混淆。
以上(shàng)就是你需要(yào)了(le)解的關于 DOM 模闆解析的所有注意事項,同時(shí)也是 Vue 基礎部分的所有內(nèi)容。祝賀你!雖然還有很(hěn)多需要(yào)學習的,但(dàn)你可(kě)以先暫停一(yī)下(xià),去用 Vue 做(zuò)一(yī)些有趣的東西(xī),或者研究一(yī)些示例。
完成了(le)本頁的閱讀後,回顧一(yī)下(xià)你剛才所學到的知識,如(rú)果還想知道(dào)更多細節,我們推薦你繼續閱讀關于組件的完整指引。
網站建設開(kāi)發|APP設計開(kāi)發|小程序建設開(kāi)發