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 逐級透傳”,顯然是我們希望盡量避免的情況。
provideinject 可以幫助我們解決這一問題。 一個父組件相對於其所有的後代組件,會作為依賴提供者。任何後代的組件樹,無論層級有多深,都可以註入由父組件提供給整條鏈路的依賴。

如下為父組件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>

provideinject 通常會在不同的組件中運行。要正確地為註入的值標記類型,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!

推薦閱讀: