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