Vue3父子組件間傳參通信的四種方式
前言
本文主要是記錄Vue3在setup語法糖下的父子組件間傳參的四種方式
Vue3+TypeScript
一、父傳子 defineProps
父組件傳值給子組件主要是由父組件為子組件通過v-bind綁定數值,而後傳給子組件;子組件則通過defineProps接收使用。
如下為父組件Father.vue
<template> <div class="fa"> <div style="margin: 10px;">我是父組件</div> <Son :fatherMessage="fatherMessage"></Son> </div> </template> <script setup lang="ts"> import Son from './Son.vue' import {ref} from "vue"; const fatherMessage = ref<string>("我是父組件傳過來的值") </script> <style scoped> .fa{ border: 3px solid cornflowerblue; width: 400px; text-align: center; } </style>
如下為子組件Son.vue
<template> <div style="margin: 10px;border: 2px solid red"> 我是子組件 <div style="margin: 5px;border: 2px solid gold"> 父組件傳值接收區:{{fatherMessage}} </div> </div> </template> <script setup lang="ts"> interface Props { fatherMessage?: string, } defineProps<Props>() </script>
父組件Father.vue
中在調用Son.vue
這個子組件時,使用v-bind
綁定參數fatherMessage
,並傳給Son.vue
子組件Son.vue
使用defineProps
接收fatherMessage
這個參數,而後就可以正常使用該參數。
二、子傳父 defineEmits
子組件傳值給父組件主要是子組件通過defineEmits
註冊一個自定義事件,而後觸發emit
去調用該自定義事件,並傳遞參數給父組件。
在父組件中調用子組件時,通過v-on
綁定一個函數,通過該函數獲取傳過來的值。
如下為子組件Son.vue
<template> <div style="margin: 10px;border: 2px solid red"> 我是子組件 <button @click="transValue" style="margin: 5px">傳值給父組件</button> </div> </template> <script setup lang="ts"> import {ref} from "vue"; // 定義所要傳給父組件的值 const value = ref<String>("我是子組件傳給父組件的值") // 使用defineEmits註冊一個自定義事件 const emit = defineEmits(["getValue"]) // 點擊事件觸發emit,去調用我們註冊的自定義事件getValue,並傳遞value參數至父組件 const transValue = () => { emit("getValue", value.value) } </script>
如下為父組件Father.vue
<template> <div class="fa"> <div style="margin: 10px;">我是父組件</div> 父組件接收子組件傳的值:{{sonMessage}} <Son @getValue="getSonValue"></Son> </div> </template> <script setup lang="ts"> import Son from './Son.vue' import {ref} from "vue"; const sonMessage = ref<string>("") const getSonValue = (value: string) => { sonMessage.value = value } </script> <style scoped> .fa{ border: 3px solid cornflowerblue; width: 400px; text-align: center; } </style>
父組件Father.vue
中在調用Son.vue
這個子組件時,當子組件Son.vue
需要傳參給父組件Father.vue
時,使用defineEmits
註冊一個事件getValue
,而後設置點擊事件transValue
去觸發emit
,去調用我們註冊的自定義事件getValue
,並傳遞value
參數至父組件。
父組件Father.vue
在獲取子組件Son.vue
傳過來的值時,通過在子組件上使用v-on
設置響應函數getValue
(該函數與子組件中的註冊自定義事件getValue
名稱需一致),並綁定一個函數getSonValue
來獲取傳過來的值。
三、子組件暴露屬性給父組件 defineExpose
當父組件想直接調用父組件的屬性或者方法時,子組件可以使用defineExpose
暴露自身的屬性或者方法,父組件中使用ref
調用子組件暴露的屬性或方法。
如下為子組件Son.vue
<template> <div style="margin: 10px;border: 2px solid red"> 我是子組件 </div> </template> <script setup lang="ts"> import {ref, defineExpose} from "vue"; // 暴露給父組件的值 const toFatherValue = ref<string>("我是要暴露給父組件的值") // 暴露給父組件的方法 const toFatherMethod = () => { console.log("我是要暴露給父組件的方法") } // 暴露方法和屬性給父組件 defineExpose({toFatherMethod, toFatherValue}) </script>
如下為父組件Father.vue
<template> <div class="fa"> <div style="margin: 10px;">我是父組件</div> <button @click="getSonMethod">獲取子組件的方法</button> <Son ref="sonMethodRef"></Son> </div> </template> <script setup lang="ts"> import Son from './Son.vue' import {ref} from "vue"; const sonMethodRef = ref() const getSonMethod = () => { sonMethodRef.value.toFatherMethod() console.log(sonMethodRef.value.toFatherValue) } </script> <style scoped> .fa{ border: 3px solid cornflowerblue; width: 400px; text-align: center; } </style>
在子組件中定義屬性toFatherValue
和方法toFatherMethod
,而後通過defineExpose
暴露出來。
父組件調用時,為子組件綁定一個ref
,並定義一個ref
變量sonMethodRef
,通過調用sonMethodRef
,來獲取子組件暴露出來的屬性和方法。
四、依賴註入Provide / Inject
從上面的介紹裡我們可以瞭解到父子組件之間的通信,但是卻存在這樣的情況:有一些多層級嵌套的組件,形成瞭一顆巨大的組件樹,而某個深層的子組件需要一個較遠的祖先組件中的部分數據。在這種情況下,如果僅使用 props 則必須將其沿著組件鏈逐級傳遞下去,這會非常麻煩:
雖然這裡的 Footer
組件可能根本不關心這些 props
,但為瞭使 DeepChild
能訪問到它們,仍然需要定義並向下傳遞。如果組件鏈路非常長,可能會影響到更多這條路上的組件。這一問題被稱為“prop 逐級透傳”,顯然是我們希望盡量避免的情況。provide
和 inject
可以幫助我們解決這一問題。 一個父組件相對於其所有的後代組件,會作為依賴提供者。任何後代的組件樹,無論層級有多深,都可以註入由父組件提供給整條鏈路的依賴。
如下為父組件Root.vue
<template> <div> 我是root組件 <Footer></Footer> </div> </template> <script setup lang="ts"> import { provide, ref } from 'vue' import Footer from './Footer.vue' const toChildValue= ref<string>("我是給所有子組件的值") // 將toChildValue註入到所有子組件中 provide(/* 註入名 */ 'toChildValue', /* 值 */ toChildValue) </script>
如下為子組件Footer.vue
<template> <div> 我是footer組件 <div> 接收父組件的值:{{getFatherValue}} </div> <DeepChild></DeepChild> </div> </template> <script setup lang="ts"> import DeepChild from "./DeepChild.vue" import {ref,inject,Ref} from "vue"; // 獲取父組件提供的值 // 如果沒有祖先組件提供 "toChildValue" // ref("") 會是 "這是默認值" const getFatherValue = inject<Ref<string>>(/* 註入名 */"toChildValue",/* 默認值 */ ref("")) </script>
如下為孫子組件DeepChild.vue
<template> <div> 我是deepChild組件 <div> 接收爺爺組件的值:{{getGrandFatherValue}} </div> </div> </template> <script setup lang="ts"> import {inject, ref, Ref} from "vue"; // 獲取爺爺組件提供的值 // 如果沒有爺爺組件提供 "toChildValue" // value 會是 "" const getGrandFatherValue = inject<Ref<string>>(/* 註入名 */"toChildValue",/* 默認值 */ ref("")) </script>
當最頂層的組件Root.vue
傳值給所有子組件時,使用provide
進行註入
provide(/* 註入名 */ 'toChildValue', /* 值 */ toChildValue)
而後無論哪個子組件想要獲取toChildValue
的值,隻需使用inject
即可
inject<Ref<string>>(/* 註入名 */"toChildValue",/* 默認值 */ ref(""))
當提供 / 註入響應式的數據時,如果想改變數據時,建議盡可能將任何對響應式狀態的變更都保持在供給方組件中,即根組件Root.vue
。這樣可以確保所提供狀態的聲明和變更操作都內聚在同一個組件內,使其更容易維護。
有的時候,我們可能需要在註入方組件中更改數據。在這種情況下,我們推薦在供給方組件內聲明並提供一個更改數據的方法函數:
如下為父組件Root.vue
<template> <div> 我是root組件 <Footer></Footer> </div> </template> <script setup lang="ts"> import {InjectionKey, provide, Ref, ref} from 'vue' import Footer from './Footer.vue' const toChildValue= ref<string>("我是給所有子組件的值") /** * 修改父組件值的方法 */ const changeValue = () => { toChildValue.value = "我是父組件修改的值" } // 定義一個註入key的類型(建議將註入 key 的類型放在一個單獨的文件中,這樣它就可以被多個組件導入) interface ProvideType { toChildValue: Ref<string>; changeValue: () => void; } // 為註入值標記類型 const toValue = Symbol() as InjectionKey<ProvideType> // 將toChildValue和changeValue註入到所有子組件中 provide(/* 註入名 */ 'toValue', /* 值 */{ toChildValue, changeValue }) </script>
provide
和 inject
通常會在不同的組件中運行。要正確地為註入的值標記類型,Vue 提供瞭一個 InjectionKey
接口,它是一個繼承自 Symbol
的泛型類型,可以用來在提供者和消費者之間同步註入值的類型。
建議將註入 key
的類型放在一個單獨的文件中,這樣它就可以被多個組件導入。
// 定義一個註入key的類型 //(建議將註入 key 的類型放在一個單獨的文件中,這樣它就可以被多個組件導入) interface ProvideType { toChildValue: Ref<string>; changeValue: () => void; } // 為註入值標記類型 const toValue = Symbol() as InjectionKey<ProvideType>
如下為孫子組件DeepChild.vue
<template> <div> 我是deepChild組件 <div> <button @click="changeValue">改變祖先組件的值</button> {{toChildValue}} </div> </div> </template> <script setup lang="ts"> import {inject, ref, Ref} from "vue"; // 定義註入值的類型 interface ProvideType { toChildValue: Ref<string>; changeValue: () => void; } // 解構獲取父組件傳的值,需要進行強制類型轉換 const {toChildValue, changeValue} = inject(/* 註入名 */"toValue") as ProvideType // 不解構時,隻需指定類型即可 // const value = inject<ProvideType>(/* 註入名 */"toValue") </script>
當祖先組件提供參數與方法時,子組件在解構時需要強制轉換該值的類型
// 解構獲取父組件傳的值 const {toChildValue, changeValue} = inject(/* 註入名 */"toValue") as ProvideType
如果子組件在使用時不進行解構,則直接指明類型即可
// 不解構時,直接指定類型即可 const value = inject<ProvideType>(/* 註入名 */"toValue")
參考
1、小滿ZS 學習Vue3 第二十三章(依賴註入Provide / Inject)
2、Vue3官網 依賴註入
總結
到此這篇關於Vue3父子組件間傳參通信的四種方式的文章就介紹到這瞭,更多相關Vue3父子組件間傳參通信內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- Vue3學習筆記之依賴註入Provide/Inject
- Vue3常用的通訊方式總結與實例代碼
- 深入瞭解Vue3組件傳值方式
- vue3中 provide 和 inject 用法及原理
- Vue 中 provide和inject的使用