目錄
前言
正文
一、選項式API與組合式API
二、生命周期函數
?1、onBeforeMount()
2、onMounted()
3、onBeforeUpdate()
4、onUpdated()
5、onBeforeUnmount()
6、onUnmounted()
三、組件之間的樣式沖突
四、父組件向子組件傳遞數據
1、定義props
2、靜態綁定props
3、動態綁定props
4、校驗props
① 基本類型檢查
② 必填項的校驗
③ 屬性默認值
④ 自定義驗證函數
5、子組件向父組件傳遞數據
① 子組件向父組件傳遞自定義事件
② 在子組件中觸發自定義事件
③ 在父組件中監聽自定義事件
前言
在學習完第三章的基礎知識后,已經可以編寫一些簡單的組件了,但是這樣的組件功能比較簡單,無法滿足實際項目開發中各種復雜的需求。為了能夠更靈活地使用組件,還需要更深入地學習組件的相關知識。
正文
一、選項式API與組合式API
Vue 3支持選項式API和組合式API。其中,選項式API是從Vue 2開始使用的一種寫法,而Vue 3新增了組合式API的寫法。選項式API是一種通過包含多個選項的對象來描述組件邏輯的API,其常用的選項包括data、methods、computed、watch等。組合式API相比于選項式API,組合式API是將組件中的數據、方法、計算屬性、偵聽器等代碼全部組合在一起,寫在setup()函數中。
選項式API寫法如下:
<script>
export default {data() {return { // 定義數據 }},methods: { // 定義方法 },computed: { // 定義計算屬性 },watch: { // 定義偵聽器 }
}
</script>
組合式API寫法如下:
<script>
import { computed, watch } from 'vue'
export default {setup() {const 數據名 = 數據值const 方法名 = () => {}const 計算屬性名 = computed(() => {})watch(偵聽器的來源, 回調函數, 可選參數)return { 數據名, 方法名, 計算屬性名 }}
}
</script>
Vue提供的選項式API和組合式API這兩種寫法可以覆蓋大部分的應用場景,它們是同一底層系統所提供的兩套不同的接口。選項式API是在組合式API的基礎上實現的。
二、生命周期函數
每個 Vue 組件實例在創建時都需要經歷一系列的初始化步驟,比如設置好數據偵聽,編譯模板,掛載實例到 DOM,以及在數據改變時更新 DOM。在此過程中,它也會運行被稱為生命周期鉤子的函數,讓開發者有機會在特定階段運行自己的代碼。
Vue生命周期如下圖所示:
-
以下將在 vite 創建的工程中,舉例說明生命周期函數
-
beforecate
和created
,它們已經被setup
方法本身所取代 -
以下鉤子都應該在組件的
setup()
階段被同步調用
?1、onBeforeMount()
作用:在組件被掛載之前被調用。(獲取不到 DOM 元素)
描述:當這個鉤子被調用時,組件已經完成了其響應式狀態的設置,但還沒有創建 DOM 節點。它即將首次執行 DOM 渲染過程。
2、onMounted()
作用:在組件掛載完成后執行。(可以獲取到DOM 元素)
描述:組件在以下情況下被視為已掛載:
-
其所有同步子組件都已經被掛載 (不包含異步組件或
<Suspense>
樹內的組件)。 -
其自身的 DOM 樹已經創建完成并插入了父容器中。注意僅當根容器在文檔中時,才可以保證組件 DOM 樹也在文檔中。
這個鉤子通常用于執行需要訪問組件所渲染的 DOM 樹相關的副作用,或是在服務端渲染應用中用于確保 DOM 相關代碼僅在客戶端執行。一般在這里進行數據網絡請求操作
下面演示onMounted()函數的用法:
-
創建src\components\onMountedDemo.vue文件用于存放演示代碼,寫入如下代碼:
<script setup> import { onBeforeMount, onMounted } from "vue"; ? console.log('setup') ? onBeforeMount(() => {console.log('onBeforeMount —— 組件掛載前執行') }) ? onMounted(() => {console.log('onMounted —— 組件掛載后執行') }) ? </script>
-
修改src\main.js文件,切換頁面中顯示的組件
import App from './components/onMountedDemo.vue'
-
保存代碼,使用瀏覽器訪問http://127.0.0.1:5173/,打開開發者控制臺,頁面效果如下圖所示:
3、onBeforeUpdate()
作用:在組件即將因為響應式狀態變更而更新其 DOM 樹之前調用。
描述:這個鉤子可以用來在 Vue 更新 DOM 之前訪問 DOM 狀態。在這個鉤子中更改狀態也是安全的。
4、onUpdated()
作用:在組件因為響應式狀態變更而更新其 DOM 樹之后調用。
描述:
-
父組件的更新鉤子將在其子組件的更新鉤子之后調用。
-
這個鉤子會在組件的任意 DOM 更新后被調用,這些更新可能是由不同的狀態變更導致的。如果你需要在某個特定的狀態更改后訪問更新后的 DOM,請使用 nextTick() 作為替代。
下面演示以上鉤子的使用方法:
-
創建src\components\onMountedDemo.vue文件用于存放演示代碼,寫入如下代碼:
<template><div><h2>count 計數為:{{count}}</h2><button @click="count++">點擊 + 1</button></div> </template> ? <script setup> import { onBeforeMount, onBeforeUpdate, onMounted, onUpdated, ref } from "vue"; ? const count = ref(0) ? console.log('setup') // 1 onBeforeMount(() => {console.log('onBeforeMount —— 組件掛載前執行') }) // 2 onMounted(() => {console.log('onMounted —— 組件掛載后執行')console.log('===============================================') }) ? // 3 onBeforeUpdate(() => { console.log('onBeforeUpdate —— 數據更新前調用') }) // 4 onUpdated(() => {console.log('onUpdated —— 數據更新后調用') }) ? </script>
-
修改src\main.js文件,切換頁面中顯示的組件
import App from './components/onUpdated.vue'
-
保存代碼,使用瀏覽器訪問http://127.0.0.1:5173/,打開開發者控制臺,點擊頁面上的“點擊 + 1” 按鈕,頁面效果如下圖所示:
5、onBeforeUnmount()
作用:組件內實例被卸載之前調用
描述:當這個鉤子被調用時,組件實例依然還保有全部的功能。
6、onUnmounted()
作用:在組件實例被卸載之后調用
描述:一個組件在以下情況下被視為已卸載:
-
其所有子組件都已經被卸載。
-
所有相關的響應式作用 (渲染作用以及
setup()
時創建的計算屬性和偵聽器) 都已經停止。
可以在這個鉤子中手動清理一些副作用,例如計時器、DOM 事件監聽器或者與服務器的連接。
以上鉤子代碼示例:
App.vue組件中,通過 v-if 指令,控制 Hello.vue 組件的顯示隱藏,來模擬組件的掛載/卸載,
下面演示卸載生命周期的用法:
-
創建src\components\unInstall.vue文件用于存放演示代碼,寫入如下代碼:
<template><div><h2>uninstall 組件</h2></div></template><script setup>import { onBeforeUnmount, onUnmounted } from "vue"; ?onBeforeUnmount(() => {console.log('onBeforeUnmount —— 組件卸載前執行')}) ?onUnmounted(() => {console.log('onUnmounted —— 組件卸載后執行')})</script>
-
在src/components/App.vue文件中顯示上面創建的unInstall.vue組件,代碼如下:
<template><div><h2>count 計數為:{{count}}</h2><button @click="count++">點擊 + 1</button><hr> ?<unInstall v-if="display"/><button @click="display=!display">掛載/卸載unInstall組件</button> ?</div> </template> ? <script setup> import { onBeforeMount, onBeforeUpdate, onMounted, onUpdated, ref } from "vue"; import unInstall from './components/unInstall.vue' ? const count = ref(0) const display = ref(true) ? console.log('setup') // 1 onBeforeMount(() => {console.log('onBeforeMount —— 組件掛載前執行') }) // 2 onMounted(() => {console.log('onMounted —— 組件掛載后執行')console.log('===============================================') }) ? // 3 onBeforeUpdate(() => { console.log('onBeforeUpdate —— 數據更新前調用') }) // 4 onUpdated(() => {console.log('onUpdated —— 數據更新后調用')console.log('===============================================') }) ? </script>
-
修改src\main.js文件,切換頁面中顯示的組件
import App from './App.vue'
-
保存代碼,使用瀏覽器訪問http://127.0.0.1:5173/,打開開發者控制臺,點擊頁面上的“掛載/卸載unInstall組件” 按鈕,頁面效果如下圖所示:
三、組件之間的樣式沖突
在默認情況下,寫在Vue組件中的樣式會全局生效,很容易造成多個組件之間的樣式沖突問題。例如,為Component1組件中的h5元素添加邊框樣式,其他組件則不添加,同時顯示在頁面上。具體代碼如下:
<!-- Component1 -->
<template><h5>組件1</h5>
</template>
<style>
h5 {border: 1px dotted black;
}
</style>
<!-- Component2 -->
<template><h5>組件2</h5>
</template>
<!-- Component3 -->
<template><h5>組件3</h5>
</template>
<!-- App.vue -->
<template><div><Componet1/><Componet2/><Componet3/></div>
</template>
<script setup>
import Componet1 from './components/Componet1.vue';
import Componet2 from './components/Componet2.vue';
import Componet3 from './components/Componet3.vue';
</script>
在main.js切換App.vue顯示,保存代碼后,使用瀏覽器訪問http://localhost:5173/,頁面效果如下:
從上圖可以看出,Component1組件、Component2組件和Component3組件中h5元素的邊框樣式都發生了改變,但是代碼中只有Component1組件設置了邊框樣式效果,說明組件之間存在樣式沖突。
導致組件之間會產生樣式沖突的原因:
導致組件之間樣式沖突的根本原因是:在單頁Web應用中,所有組件的DOM結構都是基于唯一的index.html頁面進行呈現的。每個組件中的樣式都可以影響整個頁面中的DOM元素。在Vue中可以使用scoped屬性和深度選擇器來解決組件之間的樣式沖突。Vue為<style>標簽提供了scoped屬性,用于解決組件之間的樣式沖突。
為<style>標簽添加scoped屬性后,Vue會自動為當前組件的DOM元素添加一個唯一的自定義屬性(如data-v-7ba5bd30),并在樣式中為選擇器添加自定義屬性(如.list[data-v-7ba5bc90]),從而限制樣式的作用范圍,防止組件之間的樣式沖突問題。
下面演示scoped屬性的使用:
-
修改Componet1組件的內容,修改后的完整代碼如下:
<template><h5>組件1</h5> </template> <style scoped> h5 {border: 1px dotted black; } </style>
-
保存上述代碼后,在瀏覽器中訪問http://127.0.0.1:5173/,頁面效果如下圖所示:
-
打開開發者控制臺,切換到元素面板,可以看到當<style>標簽添加scoped屬性后,h5元素和相應的選擇器被Vue自動添加了data-v-428f8930屬性,從而解決了樣式沖突的問題。
四、父組件向子組件傳遞數據
1、定義props
若想實現父組件向子組件傳遞數據,需要先在子組件中聲明props,表示子組件可以從父組件中接收哪些數據。在不使用setup語法糖的情況下,可以使用props選項聲明props。props選項的形式可以是對象或字符串數組。聲明對象形式的props的語法格式如下:
<script>
export default {props: {自定義屬性A: 類型,自定義屬性B: 類型,……}
}
</script>
如果不需要限制props的類型,可以聲明字符串數組形式的props,示例代碼如下:
props: ['自定義屬性A', '自定義屬性B'],
當使用setup語法糖時,可使用defineProps()函數聲明props,語法格式如下:
<script setup>
const props = defineProps({'自定義屬性A': 類型}, {'自定義屬性B': 類型})
</script>
使用defineProps()函數聲明字符串數組形式的props,語法格式如下:
const props = defineProps(['自定義屬性A', '自定義屬性B'])
在組件中聲明了props后,可以直接在模板中輸出每個prop的值,語法格式如下:
<template> {{ 自定義屬性A }}{{ 自定義屬性B }}
</template>
2、靜態綁定props
當在父組件中引用了子組件后,如果子組件中聲明了props,則可以在父組件中向子組件傳遞數據。如果傳遞的數據是固定不變的,則可以通過靜態綁定props的方式為子組件傳遞數據。如果子組件中未聲明props,則父組件向子組件中傳遞的數據會被忽略,無法被子組件使用。
通過靜態綁定props的方式為子組件傳遞數據,其語法格式如下:
<子組件標簽名 自定義屬性A="數據" 自定義屬性B="數據" />
在上述語法格式中,父組件向子組件的props傳遞了靜態的數據,屬性值默認為字符串類型。
下面演示父組件向子組件傳遞數據的方法:
-
創建src\components\Count.vue文件,用于展示子組件的相關內容,文件代碼如下:
<template>初始值為:{{ num }} </template> <script setup> const props = defineProps({num: String }) </script>
-
創建src\components\Props.vue文件,用于展示父組件的相關內容,文件代碼如下:
<template><Count num="1" /> </template> <script setup> import Count from './Count.vue' </script>
-
修改src\main.js文件,切換頁面中顯示的組件
import App from './components/Props.vue'
-
保存上述代碼后,在瀏覽器中訪問http://127.0.0.1:5173/,父組件向子組件中傳遞數據的頁面效果如下圖所示:
3、動態綁定props
在父組件中使用v-bind可以為子組件動態綁定props,任意類型的值都可以傳給子組件的props,包括字符串、數字、布爾值、數組、對象等。
從父組件中為子組件傳遞字符串類型的props數據,示例代碼如下:
<template><Child :init="username" /> </template> <script setup> import Child from './Child.vue' import { ref } from 'vue' const username = ref('小紅') </script>
上述代碼用到了名稱為Child的子組件,該子組件的示例代碼如下:
<template></template> <script setup> const props = defineProps(['init']) console.log(props) </script>
在Vue中,所有的props都遵循單向數據流原則,props數據因父組件的更新而變化,變化后的數據將向下流往子組件,而且不會逆向傳遞,這樣可以防止因子組件意外變更props導致數據流向難以理解的問題。 ? 每次父組件綁定的props發生變更時,子組件中的props都將會刷新為最新的值。開發者不應該在子組件內部改變props,如果這樣做,Vue會在瀏覽器的控制臺中發出警告。
4、校驗props
在封裝組件時,可以在子組件中對從父組件傳遞過來的props數據進行合法性校驗,從而防止出現數據不合法的問題。
使用字符串數組形式的props的缺點是無法為每個prop指定具體的數據類型,而使用對象形式的props的優點是可以對每個prop進行數據類型的校驗。對象形式的props可以使用多種驗證方案,包括基礎類型檢查、必填項的校驗、屬性默認值、自定義驗證函數等。在聲明props時,可以添加驗證方案。
① 基本類型檢查
在開發中,有時需要對從父組件中傳遞過來的props數據進行基礎類型檢查,這時可以通過type屬性檢查合法的類型,如果從父組件中傳遞過來的值不符合此類型,則會報錯。 ? 常見的類型有String(字符串)、Number(數字)、Boolean(布爾值)、Array(數組)、Object(對象)、Date(日期)、Function(函數)、Symbol(符號)以及任何自定義構造函數。
為props指定基礎類型檢查,示例代碼如下:
props: {自定義屬性A: String, // 字符串自定義屬性B: Number, // 數字自定義屬性C: Boolean, // 布爾值自定義屬性D: Array, // 數組自定義屬性E: Object, // 對象自定義屬性F: Date, // 日期自定義屬性G: Function, // 函數自定義屬性H: Symbol, // 符號
}
通過配置對象的形式定義驗證規則,示例代碼如下:
props: {自定義屬性: { type: Number },
}
如果某個prop的類型不唯一,可以通過數組的形式為其指定多個可能的類型,示例代碼如下:
props: {自定義屬性: { type: [String, Array] }, // 字符串或數組
}
② 必填項的校驗
父組件向子組件傳遞props數據時,有可能傳遞的數據為空,但是在子組件中要求該數據是必須傳遞的。此時,可以在聲明props時通過required屬性設置必填項,強調組件的使用者必須傳遞屬性的值,示例代碼如下:
props: {自定義屬性: { required: true },
}
③ 屬性默認值
在聲明props時,可以通過default屬性定義屬性默認值,當父組件沒有向子組件的屬性傳遞數據時,屬性將會使用默認值,示例代碼如下:
props: {自定義屬性: { default: 0 },
}
④ 自定義驗證函數
如果需要對從父組件中傳入的數據進行驗證,可以通過validator()函數來實現。validator()函數可以將prop的值作為唯一參數傳入自定義驗證函數,如果驗證失敗,則會在控制臺中發出警告。為prop屬性指定自定義驗證函數的示例代碼如下:
props: {自定義屬性: {validator(value) {return ['success', 'warning', 'danger'].indexOf(value) !== -1;},},
}
5、子組件向父組件傳遞數據
父子組件的關系可以總結為 prop 向下傳遞,emit事件向上傳遞。父組件通過prop給子組件下發數據,子組件通過事件emit給父組件發送消息。
① 子組件向父組件傳遞自定義事件
若想使用自定義事件,首先需要在子組件中聲明自定義事件。在不使用setup語法糖時,可以通過emits選項聲明自定義事件,示例代碼如下:
<script>
export default {emits: ['demo']
}
</script>
在使用setup語法糖時,需要通過調用defineEmits()函數聲明自定義事件,示例代碼如下:
<script setup>
const emit = defineEmits(['demo'])
</script>
② 在子組件中觸發自定義事件
在子組件中聲明自定義事件后,接著需要在子組件中觸發自定義事件。當使用場景簡單時,可以使用內聯事件處理器,通過調用$emit()方法觸發自定義事件,將數據傳遞給使用的組件,示例代碼如下:
<button @click="$emit('demo', 1)">按鈕</button>
在上述代碼中,$emit()方法的第1個參數為字符串類型的自定義事件的名稱,第2個參數為需要傳遞的數據,當觸發當前組件的事件時,該數據會傳遞給父組件。
除了使用內聯方式外,還可以直接定義方法來觸發自定義事件。在不使用setup語法糖時,可以從setup()函數的第2個參數(即setup上下文對象)來訪問到emit()方法,示例代碼如下:
export default {setup(props, ctx) {const update = () => {ctx.emit('demo', 2)}return { update }}
}
如果使用setup語法糖,可以調用emit()函數來實現,示例代碼如下:
<script setup>
const update = () => {emit('demo', 2)
}
</script>
③ 在父組件中監聽自定義事件
在父組件中通過v-on可以監聽子組件中拋出的事件,示例代碼如下:
<子組件名 @demo="fun" />
在上述代碼中,當觸發demo事件時,會接收到從子組件中傳遞的參數,同時會執行fun()方法。父組件可以通過value屬性接收從子組件中傳遞來的參數。在父組件中定義fun()方法,示例代碼如下:
const fun = value => {console.log(value)
}
下面演示向父組件傳遞數據的使用方法:
-
創建src\components\CustomSubComponent.vue文件,用于展示子組件的相關內容。子組件代碼如下:
<template><p>count值為:{{ count }}</p><button @click="add">加n</button> </template> <script setup> import { ref } from 'vue' const emit = defineEmits(['updateCount']) const count = ref(1) // 自身count + 1,向父組件傳值為2 const add = () => { ?count.value++ emit('updateCount', 2) } </script>
-
創建src\components\CustomEvents.vue文件,用于展示父組件的相關內容。父組件代碼如下:
<template><p>父組件當前的值為:{{ number }}</p><CustomSubComponent @updateCount="updateEmitCount" /> </template> <script setup> import CustomSubComponent from './CustomSubComponent.vue' import { ref } from 'vue' const number = ref(10) const updateEmitCount = (value) => { ?number.value += value } </script>
-
修改src\main.js文件,切換頁面中顯示的組件。
import App from './components/CustomEvents.vue'
-
保存上述代碼后,在瀏覽器中訪問http://127.0.0.1:5173/,初始頁面效果如下圖所示:
-
單擊“加n”按鈕后的頁面效果如下圖所示: