本文整理來自深入Vue3+TypeScript技術(shù)棧-coderwhy大神新課,只作為個人筆記記錄使用,請大家多支持王紅元老師阀湿。
認(rèn)識組件的嵌套
前面我們是將所有的邏輯放到一個App.vue組件中池磁,如果我們將所有的代碼邏輯都放到一個App.vue組件中,我們會發(fā)現(xiàn)清女,代碼是非常的臃腫和難以維護的。并且在真實開發(fā)中,我們會有更多的內(nèi)容和代碼邏輯结澄,對于擴展性和可維護性來說都是非常差的。所以岸夯,在真實開發(fā)中麻献,我們會對組件進行拆分,拆分成一個個功能的小組件猜扮,再將這些組件組合嵌套在一起勉吻,最終形成我們的應(yīng)用程序。
組件的拆分
原來的代碼:
我們可以按照如下的方式進行拆分:
按照如上的拆分方式后破镰,我們開發(fā)對應(yīng)的邏輯只需要去對應(yīng)的組件編寫就可餐曼。
推薦插件
Vue代碼高亮的插件:Vetur压储、Volar。
代碼片段插件:Vue VSCode Snippets源譬、Vue3 Snippets集惋。
對于組件的導(dǎo)入,不加后綴名也不會報錯踩娘,因為VueCLI是基于webpack的刮刑,而webpack又有resolve.extensions用來解析擴展名,Vue已經(jīng)內(nèi)置在extensions里面添加了.vue养渴,所以不寫.vue后綴名也可以雷绢。
但是最好帶上后綴名,比如:import Header from './Header.vue';
理卑,如果不帶有兩個問題:
- 使用組件的時候沒有提示
- 點擊路徑不會跳轉(zhuǎn)到對應(yīng)組件代碼
如果加上.vue后綴就沒有上面兩個問題了翘紊。
Vue3的scoped偶爾失效的問題
vue2中我們給樣式添加scoped就會避免樣式被污染的問題,這是因為標(biāo)簽上被添加上了一個屬性藐唠,設(shè)置樣式的時候用了屬性選擇器帆疟,有這個屬性才會設(shè)置此樣式。
但是vue3中經(jīng)常會出現(xiàn)樣式污染的問題宇立,因為vue2中組件都有根元素踪宠,但是vue3中可以沒有根元素,沒有根元素的時候就會出現(xiàn)這個bug妈嘹。因為沒有根元素柳琢,上個組件的屬性會穿透到下個組件,所以他們就有重復(fù)的屬性了润脸,樣式就會被污染柬脸,如下:
所以,Vue3組件也要有個根元素毙驯。
組件的通信
上面的嵌套邏輯如下肖粮,它們存在如下關(guān)系:
App組件是Header、Main尔苦、Footer組件的父組件涩馆;
Main組件是Banner、ProductList組件的父組件允坚;
在開發(fā)過程中魂那,我們會經(jīng)常遇到需要組件之間相互進行通信。比如App可能使用了多個Header稠项,每個地方的Header展示的內(nèi)容不同涯雅,那么我們就需要使用者傳遞給Header一些數(shù)據(jù),讓其進行展示展运。又比如我們在Main中一次性請求了Banner數(shù)據(jù)和ProductList數(shù)據(jù)活逆,那么就需要傳遞給它們來進行展示精刷。也可能是子組件中發(fā)生了事件,需要由父組件來完成某些操作蔗候,那就需要子組件向父組件傳遞事件怒允。
總之,在一個Vue項目中锈遥,組件之間的通信是非常重要的環(huán)節(jié)纫事,所以接下來我們就具體學(xué)習(xí)一下組件之間是如何相互之間傳遞數(shù)據(jù)的。
父子組件之間通信的方式
父子組件之間如何進行通信呢所灸?
- 父組件傳遞給子組件:通過props屬性丽惶;
- 子組件傳遞給父組件:通過$emit觸發(fā)事件;
父組件給子組件傳遞數(shù)據(jù)
在開發(fā)中很常見的就是父子組件之間通信爬立,比如父組件有一些數(shù)據(jù)钾唬,需要子組件來進行展示,這個時候我們可以通過props來完成組件之間的通信侠驯。
什么是Props呢知纷?
Props是你可以在組件上注冊一些自定義的attribute,父組件給這些attribute賦值陵霉,子組件通過attribute的名稱獲取到對應(yīng)的值。
Props有兩種常見的用法:
方式一:字符串?dāng)?shù)組伍绳,數(shù)組中的字符串就是attribute的名稱踊挠;
方式二:對象類型,對象類型我們可以在指定attribute名稱的同時冲杀,指定它需要傳遞的類型效床、是否是必須的、默認(rèn)值等等权谁;
Props的數(shù)組用法
上面是傳遞寫死的值剩檀,如果傳遞data里面的值就需要進行綁定:
//直接綁定
<show-message :title="title" :content="content"></show-message>
//綁定對象的屬性
<show-message :title="message.title" :content="message.content"></show-message>
//綁定對象,就會把對象的所有屬性綁定到組件上旺芽,這種寫法和上一行效果一樣
<show-message v-bind="message"></show-message>
//數(shù)據(jù)
data() {
return {
title: "嘻嘻嘻",
content: "我是嘻嘻嘻嘻",
message: {
title: "嘿嘿嘿",
content: "我是嘿嘿嘿"
}
}
}
Props的對象用法
數(shù)組用法中我們只能說明傳入的attribute的名稱沪猴,并不能對其進行任何形式的限制,接下來我們來看一下對象的寫法是如何讓我們的props變得更加完善的采章,真實開發(fā)中我們就使用對象用法运嗜。
當(dāng)使用對象語法的時候,我們可以對傳入的內(nèi)容限制更多:
- 比如指定傳入的attribute的類型悯舟;
- 比如指定傳入的attribute是否是必傳的担租;
- 比如指定沒有傳入?yún)?shù)時,attribute的默認(rèn)值抵怎;
那么type的類型都可以是哪些呢奋救?
- String
- Number
- Boolean
- Array
- Object
- Date
- Function
- Symbol
Props對象語法補充
如果有多個可能的類型岭参,按如下寫法:
props: {
//可能是String也可能是Number
IDNumber: [String, Number]
}
- 對象或數(shù)組的默認(rèn)值必須從一個工廠函數(shù)中獲取。這是因為組件是復(fù)用的尝艘,如果默認(rèn)傳遞一個對象演侯,那么這個對象(引用類型)也會被其他組件引用,所以我們傳遞一個函數(shù)利耍,返回一個對象蚌本。
- 我們也可以自定義驗證函數(shù),保證傳遞的值是我們指定的值隘梨。
Prop的大小寫命名
- 如果是瀏覽器解析程癌,因為HTML 中的 attribute 名是大小寫不敏感的,所以下面camelCase (駝峰命名)方式的的messageInfo會被解析成messageinfo轴猎,就會有問題嵌莉,所以我們可以換成等價的kebab-case (短橫線分隔命名) 寫法。
- 但是在.vue文件中捻脖,template是給vue-loader解析的锐峭,vue-loader解析的就不會有問題,所以在.vue文件中可婶,兩種寫法都可以沿癞,但是官方還是推薦kebab-case (短橫線分隔命名)。
非Prop的Attribute
什么是非Prop的Attribute呢矛渴?
當(dāng)我們傳遞給一個組件某個屬性椎扬,但是該屬性并沒有定義對應(yīng)的props或者emits時,就稱之為非Prop的Attribute具温,常見的包括class蚕涤、style、id屬性等铣猩。
Attribute繼承:當(dāng)組件有單個根節(jié)點時揖铜,非Prop的Attribute將自動添加到組件的根節(jié)點的Attribute中。想想也很容易理解达皿,因為給組件添加屬性沒啥意義啊天吓,所以只要組件有單個根節(jié)點就會把屬性添加到單個根節(jié)點上。
如果我們不希望組件的根節(jié)點繼承attribute峦椰,可以在組件中設(shè)置 inheritAttrs: false
失仁。禁用attribute繼承的常見情況是需要將attribute應(yīng)用于根元素之外的其他元素。比如下面我們不想將class屬性添加到div上们何,可以在組件中設(shè)置inheritAttrs: false
萄焦,然后通過$attrs來訪問所有的非props的attribute,然后再設(shè)置到h2上就行了。
當(dāng)非props的attribute比較多的時候拂封,我們也可以直接綁定對象:
<div>
我是NotPropAttribute組件
<h2 v-bind="$attrs"></h2>
</div>
多個根節(jié)點的attribute如果沒有顯示的綁定茬射,那么會報警告,我們必須手動的指定要綁定到哪一個屬性上:
子組件給父組件傳遞數(shù)據(jù)
什么情況下子組件需要傳遞內(nèi)容到父組件呢冒签?
當(dāng)子組件有一些事件發(fā)生的時候在抛,比如在組件中發(fā)生了點擊,父組件需要切換內(nèi)容萧恕;
子組件有一些內(nèi)容想要傳遞給父組件的時候刚梭;
我們?nèi)绾瓮瓿缮厦娴牟僮髂兀?br> 首先,我們需要在子組件中定義好在某些情況下觸發(fā)的事件名稱票唆,其次朴读,在父組件中以v-on的方式傳入要監(jiān)聽的事件名稱,并且綁定到對應(yīng)的方法中走趋,最后衅金,在子組件中發(fā)生某個事件的時候,根據(jù)事件名稱觸發(fā)對應(yīng)的事件簿煌。
傳遞流程
我們封裝一個CounterOperation.vue的組件氮唯,內(nèi)部其實是監(jiān)聽兩個按鈕的點擊,點擊之后通過this.$emit的方式發(fā)出去事件姨伟。
子組件通過$emit觸發(fā)事件惩琉,并且通過emits指明我們都有哪些觸發(fā)事件。
父組件通過v-on監(jiān)聽事件:
上面代碼和vue2比夺荒,其實就是多了emits: ["add", "sub", "addN"]瞒渠。
傳遞參數(shù)和驗證
自定義事件的時候,我們也可以傳遞一些參數(shù)給父組件:
在vue3中般堆,emits除了是數(shù)組,還可以是個對象诚啃,對象寫法的目的是為了進行參數(shù)的驗證淮摔。實際開發(fā)中,emits一般我們都是使用數(shù)組始赎。
// emits: ["add", "sub", "addN"],
// 對象寫法的目的是為了進行參數(shù)的驗證
emits: {
add: null, //不需要驗證
sub: null,
addN: (num, name, age) => {
console.log(num, name, age);
if (num > 10) {
return true
}
return false; //如果驗證不通過和橙,參數(shù)還是可以傳過去,只不過會報警告
}
}
組件間通信案例練習(xí)
我們來做一個相對綜合的練習(xí):父組件給子組件傳遞標(biāo)題數(shù)據(jù)造垛,點擊子組件通知父組件切換到相應(yīng)頁面魔招。
父組件App.vue代碼:
<template>
<div>
<tab-control :titles="titles" @titleClick="titleClick"></tab-control>
<h2>{{contents[currentIndex]}}</h2>
</div>
</template>
<script>
import TabControl from './TabControl.vue';
export default {
components: {
TabControl
},
data() {
return {
titles: ["衣服", "鞋子", "褲子"],
contents: ["衣服頁面", "鞋子頁面", "褲子頁面"],
currentIndex: 0
}
},
methods: {
titleClick(index) {
this.currentIndex = index;
}
}
}
</script>
<style scoped>
</style>
子組件TabControl.vue代碼:
<template>
<div class="tab-control">
<div class="tab-control-item"
:class="{active: currentIndex === index}"
v-for="(title, index) in titles"
:key="title"
@click="itemClick(index)">
<!-- span里面放標(biāo)題, 設(shè)置下面的紅色橫線 -->
<span>{{title}}</span>
</div>
</div>
</template>
<script>
export default {
emits: ["titleClick"],
props: {
titles: {
type: Array,
default() {
return []
}
}
},
data() {
return {
currentIndex: 0
}
},
methods: {
itemClick(index) {
this.currentIndex = index;
this.$emit("titleClick", index);
}
}
}
</script>
<style scoped>
.tab-control {
display: flex;
}
.tab-control-item {
flex: 1;
text-align: center;
}
.tab-control-item.active {
color: red;
}
.tab-control-item.active span {
border-bottom: 3px solid red;
padding: 5px 10px;
}
</style>