元件基礎
元件允許我們將UI拆分為獨立和可重用的部分,並單獨考慮每個部分。一個應用通常組織成巢狀元件的樹
這與我們巢狀原生的HTML元素非常相似,但Vue實現了自己的元件模型,允許我們在每個元件中封裝自定義內容和邏輯。Vue也與原生的Web元件很好地配合。如果您對Vue元件和原生Web元件之間的關係感興趣,請在此瞭解更多。
定義元件
當使用構建步驟時,我們通常使用.vue
副檔名定義每個Vue元件在專用檔案中,稱為單檔案元件(SFC)
vue
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>
<template>
<button @click="count++">You clicked me {{ count }} times.</button>
</template>
當不使用構建步驟時,Vue元件可以定義為一個包含Vue特定選項的普通JavaScript物件
js
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
return { count }
},
template: `
<button @click="count++">
You clicked me {{ count }} times.
</button>`
// Can also target an in-DOM template:
// template: '#my-template-element'
}
這裡將模板內聯為JavaScript字串,Vue會即時編譯它。您還可以使用ID選擇器指向一個元素(通常是原生的<template>
元素)- Vue將使用其內容作為模板源。
上述示例定義了一個單個元件,並將其作為 .js
檔案的預設匯出匯出,但您可以使用命名匯出從同一檔案中匯出多個元件。
使用元件
提示
本指南的其餘部分將使用 SFC 語法 - 無論是否使用構建步驟,元件的概念都是相同的。《示例》部分顯示了兩種情況下的元件使用。
要使用子元件,我們需要在父元件中匯入它。假設我們將計數器元件放在名為 ButtonCounter.vue
的檔案中,該元件將作為檔案的預設匯出
vue
<script setup>
import ButtonCounter from './ButtonCounter.vue'
</script>
<template>
<h1>Here is a child component!</h1>
<ButtonCounter />
</template>
使用 <script setup>
,匯入的元件將自動對模板可用。
還可以全域性註冊元件,使其在給定應用程式的所有元件中可用,而無需匯入。全域性註冊與本地註冊的優缺點在專門的《元件註冊》部分中討論。
元件可以按需重複使用
模板
<h1>Here are many child components!</h1>
<ButtonCounter />
<ButtonCounter />
<ButtonCounter />
請注意,當點選按鈕時,每個按鈕都保持自己的獨立 count
。這是因為每次使用元件時,都會建立一個新的 例項。
在 SFC 中,建議使用 PascalCase
標籤名來命名子元件,以便與原生 HTML 元素區分開來。雖然原生 HTML 標籤名不區分大小寫,但 Vue SFC 是一個編譯格式,因此我們能夠在其中使用大小寫敏感的標籤名。我們還可以使用 />
來關閉標籤。
如果您直接在 DOM 中編寫模板(例如,作為原生 <template>
元素的內容),則模板將受瀏覽器原生 HTML 解析行為的影響。在這種情況下,您需要使用 kebab-case
和顯式關閉標籤來為元件
模板
<!-- if this template is written in the DOM -->
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
有關更多詳細資訊,請參閱 in-DOM 模板解析注意事項。
傳遞 Props
如果我們正在構建一個部落格,我們可能需要一個表示部落格文章的元件。我們希望所有部落格文章都具有相同的視覺佈局,但內容不同。這樣的元件除非您可以向它傳遞資料(例如,我們想要顯示的特定文章的標題和內容),否則將沒有用處。這正是 props 的作用。
Props 是您可以註冊在元件上的自定義屬性。要向我們的部落格文章元件傳遞標題,我們必須在元件接受的 props 列表中宣告它,使用 defineProps
宏
vue
<!-- BlogPost.vue -->
<script setup>
defineProps(['title'])
</script>
<template>
<h4>{{ title }}</h4>
</template>
defineProps
是一個編譯時宏,僅在 <script setup>
內部可用,無需顯式匯入。宣告的 props 將自動暴露給模板。defineProps
還返回一個包含傳遞給元件的所有 props 的物件,以便我們可以在需要時在 JavaScript 中訪問它們
js
const props = defineProps(['title'])
console.log(props.title)
另請參閱:為元件 Props 型別化
如果您不使用 <script setup>
,則應使用 props
選項來宣告屬性,並且屬性物件將被傳遞給 setup()
作為第一個引數
js
export default {
props: ['title'],
setup(props) {
console.log(props.title)
}
}
元件可以有任意多的屬性,並且預設情況下,任何值都可以傳遞給任何屬性。
一旦註冊了屬性,您就可以像這樣將其作為自定義屬性傳遞資料
模板
<BlogPost title="My journey with Vue" />
<BlogPost title="Blogging with Vue" />
<BlogPost title="Why Vue is so fun" />
然而,在典型的應用程式中,您可能在父元件中有一個帖子陣列
js
const posts = ref([
{ id: 1, title: 'My journey with Vue' },
{ id: 2, title: 'Blogging with Vue' },
{ id: 3, title: 'Why Vue is so fun' }
])
然後想要使用 v-for
為每個帖子渲染一個元件
模板
<BlogPost
v-for="post in posts"
:key="post.id"
:title="post.title"
/>
請注意,如何使用 v-bind
語法 (:title="post.title"
)來傳遞動態屬性值。這在您不知道將要渲染的確切內容時非常有用。
這就是您現在需要了解的所有關於屬性的內容,但是一旦您完成閱讀此頁面並熟悉其內容,我們建議稍後返回閱讀關於 屬性 的完整指南。
監聽事件
隨著我們開發 <BlogPost>
元件,一些功能可能需要向上與父元件通訊。例如,我們可能決定包含一個輔助功能來放大部落格文章的文字,同時保持頁面其餘部分預設大小。
在父元件中,我們可以透過新增一個 postFontSize
ref
js
const posts = ref([
/* ... */
])
const postFontSize = ref(1)
這可以在模板中使用來控制所有部落格文章的字型大小
模板
<div :style="{ fontSize: postFontSize + 'em' }">
<BlogPost
v-for="post in posts"
:key="post.id"
:title="post.title"
/>
</div>
現在讓我們在 <BlogPost>
元件的模板中新增一個按鈕
vue
<!-- BlogPost.vue, omitting <script> -->
<template>
<div class="blog-post">
<h4>{{ title }}</h4>
<button>Enlarge text</button>
</div>
</template>
按鈕目前沒有任何功能 - 我們希望點選按鈕能讓父元件知道應該放大所有帖子的文字。為了解決這個問題,元件提供了一個自定義事件系統。父元件可以選擇使用 v-on
或 @
來監聽子元件例項上的任何事件,就像我們使用原生DOM事件一樣
模板
<BlogPost
...
@enlarge-text="postFontSize += 0.1"
/>
然後子元件可以透過呼叫內建的 $emit
方法 在自己上發出事件,傳遞事件的名稱
vue
<!-- BlogPost.vue, omitting <script> -->
<template>
<div class="blog-post">
<h4>{{ title }}</h4>
<button @click="$emit('enlarge-text')">Enlarge text</button>
</div>
</template>
多虧了 @enlarge-text="postFontSize += 0.1"
監聽器,父元件將接收到事件並更新 postFontSize
的值。
我們可以選擇性地使用 defineEmits
宏 來聲明發出的事件
vue
<!-- BlogPost.vue -->
<script setup>
defineProps(['title'])
defineEmits(['enlarge-text'])
</script>
這記錄了元件發出的所有事件,並可選擇 驗證它們。它還允許Vue避免將它們隱式地應用於子元件根元素的原生監聽器。
與 defineProps
類似,defineEmits
只能在 <script setup>
中使用,且不需要匯入。它返回一個等同於 $emit
方法的 emit
函式。它可以在元件的 <script setup>
部分中使用,在那裡 $emit
不能直接訪問
vue
<script setup>
const emit = defineEmits(['enlarge-text'])
emit('enlarge-text')
</script>
另請參閱: 元件發出事件的型別化
如果您不使用 <script setup>
,您可以使用 emits
選項來聲明發出的事件。您可以將 emit
函式作為 setup 上下文的一個屬性訪問,它作為第二個引數傳遞給 setup()
js
export default {
emits: ['enlarge-text'],
setup(props, ctx) {
ctx.emit('enlarge-text')
}
}
現在您需要了解關於自定義元件事件的全部內容,但是當您閱讀完本頁並對其內容感到熟悉後,我們建議您稍後回來閱讀關於自定義事件的完整指南。
使用插槽進行內容分發
就像HTML元素一樣,經常需要將內容傳遞給元件,如下所示
模板
<AlertBox>
Something bad happened.
</AlertBox>
可能渲染出類似的內容
這是一個用於演示的錯誤
發生了某些事情。
這可以透過使用Vue的自定義<slot>
元素實現
vue
<!-- AlertBox.vue -->
<template>
<div class="alert-box">
<strong>This is an Error for Demo Purposes</strong>
<slot />
</div>
</template>
<style scoped>
.alert-box {
/* ... */
}
</style>
如您所看到的,我們使用<slot>
作為內容要放置的佔位符 - 這就完成了。我們已經完成了!
現在您需要了解關於插槽的全部內容,但是當您閱讀完本頁並對其內容感到熟悉後,我們建議您稍後回來閱讀關於插槽的完整指南。
動態元件
有時,在標籤頁介面中動態切換元件是有用的
上面的內容是透過Vue的<component>
元素和特殊的is
屬性實現的
模板
<!-- Component changes when currentTab changes -->
<component :is="tabs[currentTab]"></component>
在上面的示例中,傳遞給:is
的值可以是
- 已註冊元件的名稱字串,或者
- 實際匯入的元件物件
您還可以使用is
屬性來建立常規HTML元素。
當使用<component :is="...">
在多個元件之間切換時,當切換到其他元件時,元件將被解除安裝。我們可以使用內建的<KeepAlive>
元件強制非活動元件保持“活躍”。
in-DOM 模板解析注意事項
如果您直接在DOM中編寫Vue模板,Vue將不得不從DOM中檢索模板字串。由於瀏覽器原生HTML解析行為,這導致了一些注意事項。
提示
以下討論的限制僅適用於您直接在DOM中編寫模板的情況。它們不適用於您從以下來源使用字串模板的情況
- 單檔案元件
- 內聯模板字串(例如
template: '...'
) <script type="text/x-template">
不區分大小寫
HTML標籤和屬性名稱不區分大小寫,因此瀏覽器將任何大寫字元解釋為小寫。這意味著當您使用in-DOM模板時,PascalCase元件名稱和camelCased屬性名或v-on
事件名都需要使用它們的kebab-cased(短橫線分隔)等效名稱
js
// camelCase in JavaScript
const BlogPost = {
props: ['postTitle'],
emits: ['updatePost'],
template: `
<h3>{{ postTitle }}</h3>
`
}
模板
<!-- kebab-case in HTML -->
<blog-post post-title="hello!" @update-post="onUpdatePost"></blog-post>
自閉合標籤
我們在之前的程式碼示例中使用了元件的自閉合標籤
模板
<MyComponent />
這是因為Vue的模板解析器將/>
視為結束任何標籤的指示,無論其型別如何。
然而,在in-DOM模板中,我們必須始終包含顯式的關閉標籤
模板
<my-component></my-component>
這是因為在HTML規範中,只有少數幾個特定元素可以省略閉合標籤,最常見的有<input>
和<img>
。對於所有其他元素,如果你省略了閉合標籤,原生的HTML解析器會認為你從未結束開標籤。例如,以下程式碼片段
模板
<my-component /> <!-- we intend to close the tag here... -->
<span>hello</span>
會被解析為
模板
<my-component>
<span>hello</span>
</my-component> <!-- but the browser will close it here. -->
元素放置限制
一些HTML元素,如<ul>
、<ol>
、<table>
和<select>
,對其內部可以出現哪些元素有限制,而一些元素如<li>
、<tr>
和<option>
只能出現在某些其他元素內部。
這在使用具有此類限制的元素元件時會導致問題。例如
模板
<table>
<blog-post-row></blog-post-row>
</table>
自定義元件<blog-post-row>
將被提升為無效內容,導致最終渲染輸出中的錯誤。我們可以使用特殊的is
屬性作為解決方案
模板
<table>
<tr is="vue:blog-post-row"></tr>
</table>
提示
當用於原生HTML元素時,is
的值必須以vue:
為字首,才能被視為Vue元件。這是為了避免與原生的自定義內建元素混淆。
這就是目前你需要了解的DOM模板解析的注意事項——實際上,也是Vue的《基礎知識》的結束。恭喜!還有很多東西要學,但首先,我們建議你先休息一下,親自嘗試一下Vue——構建一些有趣的東西,或者檢視一些示例,如果你還沒有的話。
一旦你對你剛剛吸收的知識感到自信,繼續閱讀指南,深入學習元件。