做自(zì)由與創造的先行者

組件基礎

Vue.js中文(wén)手冊

組件允許我們将 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)發
下(xià)一篇:組件注冊
上(shàng)一篇:模闆引用(yòng)