Vue3父子组件通信

父传子defineProps

父组件传值给子组件主要是由父组件为子组件通过v-bind绑定数值,而后传给子组件;子组件则通过defineProps接收使用。

father.vue

复制代码
<template>
    <div>
        <son :fatherProps="fatherProps"></son>
    </div>
</template>

<script lang="ts" setup>
import son from './son.vue';
import { ref } from 'vue';
const fatherProps = ref<string>('title');
</script>

Son.vue

复制代码
<template>
    <div>
        {{ fatherProps }}
    </div>
</template>

<script lang="ts" setup>
interface Props {
    fatherProps?: 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>

父组件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>

在子组件中定义属性toFatherValue和方法toFatherMethod,而后通过defineExpose暴露出来。

父组件调用时,为子组件绑定一个ref,并定义一个ref变量sonMethodRef,通过调用sonMethodRef,来获取子组件暴露出来的属性和方法。

InstanceType

有时候我们模板引用,但是在使用的时候,ts提示却不行,没有提示组件通过defineExpose暴露的方法名称,虽然这不是很影响,但是可以解决还是可以解决下~

复制代码
<!-- MyModal.vue -->
<script setup lang="ts">
import { ref } from 'vue'

const sayHello = () => (console.log('我会说hello'))

defineExpose({
  sayHello
})
</script>

然后我们在父级使用,输入完成MyModalRef.value会发现没有sayHello这个函数提示,所以这个时候我们就需要使用InstanceType 工具类型来获取其实例类型

复制代码
<!-- App.vue -->
<script setup lang="ts">
import MyModal from './MyModal.vue'
const MyModalRef = ref()

const handleOperation = () => {
  MyModalRef.value.sayHello
}
</script>

使用InstanceType 工具类型获取其实例类型:

复制代码
<!-- MyModal.vue -->
<script setup lang="ts">
import { ref } from 'vue'

const sayHello = () => (console.log('我会说hello'))

defineExpose({
  open
})
</script>

father.vue

复制代码
<!-- App.vue -->
<script setup lang="ts">
import MyModal from './MyModal.vue'

const MyModalRef = ref<InstanceType<typeof MyModal> | null>(null)

const handleOperation = () => {
  MyModalRef.value?.sayHello()
}
</script>
依赖注入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>
    我是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>

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 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60612 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了2 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅2 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅3 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅3 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment3 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅3 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊3 小时前
jwt介绍
前端
爱敲代码的小鱼3 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax