讓我們繼續關注我們的 BlogPost 組件。我們會發現有時候它需要與父組件進行交互。例如,要在此處實現無障礙訪問的需求,將博客文章的文字能夠放大,而頁面的其餘部分仍使用默認字號。
在父組件中,我們可以添加一個 postFontSize ref 來實現這個效果:
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 組件添加一個按鈕:
<!-- BlogPost.vue, 省略了 <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 方法,通過傳入事件名稱來拋出一個事件:
<!-- BlogPost.vue, 省略了 <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 宏來聲明需要拋出的事件:
<!-- BlogPost.vue -->
<script setup>
defineProps(['title'])
defineEmits(['enlarge-text'])
</script>
這聲明了一個組件可能觸發的所有事件,還可以對事件的參數進行驗證。同時,這還可以讓 Vue 避免將它們作為原生事件監聽器隱式地應用於子組件的根元素。
和 defineProps 類似,defineEmits 僅可用於 script setup 之中,並且不需要導入,它返回一個等同於 $emit 方法的 emit 函數。它可以被用於在組件的 script setup 中拋出事件,因為此處無法直接訪問 $emit:
<script setup>
const emit = defineEmits(['enlarge-text'])
emit('enlarge-text')
</script>
如果你沒有在使用 script setup,你可以通過 emits 選項定義組件會拋出的事件。你可以從 setup() 函數的第二個參數,即 setup 上下文對象上訪問到 emit 函數:
export default {
emits: ['enlarge-text'],
setup(props, ctx) {
ctx.emit('enlarge-text')
}
}
一些情況下我們會希望能和 HTML 元素一樣向組件中傳遞內容:
<AlertBox>
Something bad happened.
</AlertBox>
我們期望能渲染成這樣:
這可以通過 Vue 的自定義 slot 元素來實現:
<!-- 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 作為一個佔位符,父組件傳遞進來的內容就會渲染在這裡。
有些場景會需要在兩個組件間來回切換,例如 Tab 界面:
上面的例子是通過 Vue 的 component 元素和特殊的 is attribute 實現的:
<!-- currentTab 改變時組件也改變 -->
<component :is="tabs[currentTab]"></component>
在上面的例子中,被傳給 :is 的值可以是以下幾種:
你也可以使用 is attribute 來創建一般的 HTML 元素。
當使用 component :is="..." 來在多個組件間作切換時,被切換掉的組件會被卸載。我們可以通過 KeepAlive 組件強制讓被切換掉的組件仍然保持“存活”的狀態。
如果你想在 DOM 中直接書寫 Vue 模板,Vue 則必須從 DOM 中獲取模板字符串。由於瀏覽器的原生 HTML 解析行為限制,有一些需要注意的事項。
請注意下面討論只適用於直接在 DOM 中編寫模板的情況。如果你使用來自以下來源的字符串模板,就不需要顧慮這些限制了:
HTML 標籤和屬性名稱是不分大小寫的,所以瀏覽器會把任何大寫的字符解釋為小寫。這意味著當你使用 DOM 內的模板時,無論是 PascalCase 形式的組件名稱、camelCase 形式的 prop 名稱還是 v-on 的事件名稱,都需要轉換為相應等價的 kebab-case (短橫線連字符) 形式:
// JavaScript 中的 camelCase
const BlogPost = {
props: ['postTitle'],
emits: ['updatePost'],
template: `
<h3>{{ postTitle }}</h3>
`
}
<!-- HTML 中的 kebab-case -->
<blog-post post-title="hello!" @update-post="onUpdatePost"></blog-post>
我們在上面的例子中已經使用過了閉合標籤 (self-closing tag):
<MyComponent />
這是因為 Vue 的模板解析器支持任意標籤使用 /> 作為標籤關閉的標誌。
然而在 DOM 內模板中,我們必須顯式地寫出關閉標籤:
<my-component></my-component>
這是由於 HTML 只允許一小部分特殊的元素省略其關閉標籤,最常見的就是 和 。對於其他的元素來說,如果你省略了關閉標籤,原生的 HTML 解析器會認為開啟的標籤永遠沒有結束,用下面這個代碼片段舉例來說:
<my-component /> <!-- 我們想要在這裡關閉標籤... -->
<span>hello</span>
將被解析為:
<my-component>
<span>hello</span>
</my-component> <!-- 但瀏覽器會在這裡關閉標籤 -->
某些 HTML 元素對於放在其中的元素類型有限制,例如 ul,ol,table 和 select,相應的,某些元素僅在放置於特定元素中時才會顯示,例如 li,tr 和 option。
這將導致在使用帶有此類限制元素的組件時出現問題。例如:
<table>
<blog-post-row></blog-post-row>
</table>
自定義的組件 blog-post-row 將作為無效的內容被忽略,因而在最終呈現的輸出中造成錯誤。我們可以使用特殊的 is attribute 作為一種解決方案:
<table>
<tr is="vue:blog-post-row"></tr>
</table>
當使用在原生 HTML 元素上時,is 的值必須加上前綴 vue: 才可以被解析為一個 Vue 組件。為了避免和原生的自定義內置元素相混淆,這一點是必要的。
這一個月花了不少時間來學vue3,雖然都是很基礎的東西,但我自己也是邊學邊寫文章,希望可以對跟我一樣的新手有幫助!!