当前位置: 首页 > news >正文

vue3的组件通信方式汇总

在 Vue3 中,组件通信方式根据组件关系(父子、祖孙、兄弟、跨级等)的不同,有多种实现方式,所以选择合适的通信方式可以使我们的开发事半功倍。

一、Props / Emits

1.1 Props (父向子通信)

适用场景: 父子组件之前传值、兄弟组件通过父组件传值通信

原理:父组件通过属性绑定传递数据,子组件通过 props 接收并使用。

实现步骤:

  1. 父组件在引用子组件时,通过 v-bind(简写 :)传递数据:

    <template><div class="comp-parent"><!-- title属于静态属性 通过v-bind绑定响应式数据count属性 --><CompChild title="子组件标题" :count="count" /></div>
    </template>
    <script setup lang="ts">
    import { ref } from 'vue'
    import CompChild from './comp-child.vue'
    const count = ref(0)
    </script>
  2. 子组件通过 defineProps 定义接收的 props(支持类型校验、默认值等):

<template><div class="comp-child"><h1>{{ title }}</h1><p>当前计数:{{ count }}</p></div>
</template><script setup lang="ts">
// 方式一:通过defineProps定义子组件接收的属性
// defineProps({
//   title: {
//     type: String,
//     default: '',
//   },
//   count: {
//     type: Number,
//     default: 0,
//   },
// })
// 方式二: 使用 TypeScript 类型注解
defineProps<{title: stringcount?: number
}>()
</script>

1.2 Emits(自定事件:子向父传值)

适用场景: 子组件向上传值

原理: 子组件通过触发自定义事件传递数据,父组件监听事件并接收数据。

实现步骤:

  1. 子组件通过触发自定义事件传递数据,父组件监听事件并接收数据。

    <template><div class="comp-parent"><!-- title属于静态属性 通过v-bind绑定响应式数据count属性, 子组件通过addCount事件触发父组件更新count --><CompChild title="子组件标题" :count="count" @addCount="addCount" /></div>
    </template><script setup lang="ts">
    import { ref } from 'vue'
    import CompChild from './comp-child.vue'
    const count = ref(0)
    // 定义addCount方法
    const addCount = (val: number) => {console.log('接受子组件count加一事件: ', val)count.value = val
    }
    </script>
  2. 父组件监听子组件的自定义事件,通过回调接收数据:

<template><div class="comp-child"><h1>{{ title }}</h1><p>当前计数:{{ count }}</p><button @click="handleClick">count加一</button></div>
</template><script setup lang="ts">
const props = defineProps<{title: stringcount?: number
}>()
// 使用 defineEmits
const emit = defineEmits(['addCount'])const handleClick = () => {if (typeof props?.count !== 'undefined') {emit('addCount', props.count + 1)}
}
</script>

二、 V-model(父子双向绑定)

适用场景: 父子组件传值通信

原理: 基于 propsemits 的语法糖,简化父子组件数据双向同步。

实现步骤:

  1. 父组件使用 v-model 绑定数据:

    <template><div class="comp-parent"><CompChild v-model:count="count" /><!-- 等价于:<CompChild :modelValue="count" @update:modelValue="count = $event" /> --></div>
    </template><script setup lang="ts">
    import { ref } from 'vue'
    import CompChild from './comp-child.vue'
    const count = ref(0)
    </script>
  2. 子组件通过 modelValue 接收数据,通过 update:modelValue 事件更新:

    <template><div class="comp-child"><p>当前计数:{{ count }}</p><button @click="handleClick">count加一</button></div>
    </template><script setup lang="ts">
    const props = defineProps<{count: number
    }>()
    // 使用 defineEmits
    const emit = defineEmits(['update:count'])
    const handleClick = () => {if (typeof props?.count !== 'undefined') {emit('update:count', props.count + 1)}
    }
    </script>

三、 ref / 模板引用(父组件访问子组件实例)

适用场景: 父子组件传值

原理: 父组件通过 ref 获取子组件实例,直接访问子组件的属性或方法(需子组件主动暴露)。

实现步骤:

  1. 子组件通过 defineExpose 暴露需要被父组件访问的内容:

    <template><div class="comp-child"><h1>{{ title }}</h1></div>
    </template><script setup lang="ts">
    import { ref } from 'vue'
    const title = ref<string>('子组件标题')
    // 暴露给父组件属性和方法
    defineExpose({title,changeTitle(newTitle: string) {title.value = newTitle},
    })
    </script>
  2. 父组件通过 ref 绑定子组件,获取实例并访问:

<template><div class="comp-parent"><CompChild ref="childRef" /></div>
</template><script setup lang="ts">
import { ref, onMounted } from 'vue'
import CompChild from './comp-child.vue'
// 子组件实例引用
const childRef = ref<typeof CompChild>()
// 实例挂载完成后调用
onMounted(() => {console.log('子组件标题:', childRef.value?.title)childRef.value?.changeTitle('父组件更改的子组件标题')
})
</script>

四、Provide / Inject (依赖注入)

适用场景: 父子组件传值、祖先组件传值

原理: 祖先组件通过 provide 提供数据,任意后代组件通过 inject 注入并使用,无视层级。

使用步骤:

  1. 祖先组件通过provide提供数据:

    <template><div class="comp-parent"><CompChild /></div>
    </template><script setup lang="ts">
    import { ref, provide } from 'vue'
    import CompChild from './comp-child.vue'
    // 用户信息
    const userInfo = ref({name: '懒羊羊我小弟',age: 18,
    })
    // 传给子孙的属性和方法
    provide('userInfo', userInfo)
    </script>
  2. 后代组件(子、孙等)通过inject 注入数据:

    <template><div class="comp-child"><h1>{{ userInfo?.name }}</h1></div>
    </template>
    <script setup lang="ts">
    // 接收祖先传递的参数
    import { inject } from 'vue'
    // 接收祖先传递的用户信息
    const userInfo = inject<{ name: string; age: number }>('userInfo')
    </script>
    

五、 Event Bus (全局事件总线)

适用场景: 兄弟组件,没有直接关系的组件

原理:通过一个全局的事件中心,组件可触发事件或监听事件,实现解耦通信。

实现步骤:

  1. 定义一个事件总线实例

    // eventBus.js
    import { ref } from 'vue'class EventBus {constructor() {this.events = ref({})}$on(event, callback) {if (!this.events.value[event]) {this.events.value[event] = []}this.events.value[event].push(callback)}$off(event, callback) {if (!this.events.value[event]) returnif (callback) {this.events.value[event] = this.events.value[event].filter((cb) => cb !== callback)} else {delete this.events.value[event]}}$emit(event, ...args) {if (this.events.value[event]) {this.events.value[event].forEach((callback) => {callback(...args)})}}
    }export const eventBus = new EventBus()// 或者使用 mitt 库
    // import mitt from 'mitt'
    // export const eventBus = mitt()
  2. 在需要使用的组件中引入实例并监听自定函数

    <!--> 父组件<-->
    <template><div class="comp-parent"><CompChild /></div>
    </template><script setup lang="ts">
    import { onMounted } from 'vue'
    import CompChild from './comp-child.vue'
    import { eventBus } from '@/utils/event-bus'// 触发事件
    onMounted(() => {eventBus.$emit('userInfoChange', { name: '懒羊羊我小弟', age: 18 })
    })
    </script><!--> 子组件组件<-->
    <template><div class="comp-child"><h1>{{ userInfo?.name }}</h1></div>
    </template>
    <script setup lang="ts">
    // 接收祖先传递的参数
    import { ref, onMounted, onUnmounted } from 'vue'
    import { eventBus } from '@/utils/event-bus'
    const userInfo = ref<{ name: string; age: number }>({ name: '', age: 0 })
    // 监听事件
    onMounted(() => {eventBus.$on('userInfoChange', (newUserInfo: { name: string; age: number }) => {userInfo.value = newUserInfo})
    })
    // 取消监听事件
    onUnmounted(() => {eventBus.$off('userInfoChange')
    })
    </script>

六、 Attrs(属性透传)

适用场景: 父子组件通信、组件库组件的属性继承

原理: 当组件嵌套层级较深时(如 A → B → C),若 A 要给 C 传递属性,无需 B 手动通过 props 接收再传递给 C,而是可以通过 B 中的 $attrs 直接 “透传” 给 C,减少中间层的代码冗余。

实现步骤:

  1. 传递属性给子组件,但子组件不声明 props

    <template><div class="comp-parent"><CompChild id="comp-child" :userInfo="userInfo" @changeTitle="changeTitle" /></div>
    </template><script setup lang="ts">
    import { ref } from 'vue'
    import CompChild from './comp-child.vue'
    const userInfo = ref<{ name: string; age: number }>({ name: '懒羊羊我小弟', age: 18 })
    // 定义一个方法,用于改变子组件的title
    const changeTitle = (title: string) => {userInfo.value.name = title
    }
    </script>
  2. 子组件通过useAttrs获取到未被defineProps获取的属性值

    <template><div class="comp-child"><h1>{{ userInfo?.name }}</h1></div>
    </template>
    <script setup lang="ts">
    import { useAttrs, onMounted } from 'vue'
    // 获取未被defineProps定义的属性
    const $attrs = useAttrs()
    console.log('attrs: ', $attrs)// 接收需要的props传参
    defineProps({userInfo: {type: Object,default: () => {},},
    })onMounted(() => {if ($attrs.onChangeTitle) {// 调用子组件传递的方法;($attrs.onChangeTitle as (title: string) => void)('懒羊羊我大哥')}
    })
    </script>

    补充:

    1. 默认情况下,$attrs 中的属性会自动添加到组件的根元素上(除了已被 props 声明的)。若想禁用这一行为,可在组件中设置 inheritAttrs: false

      <!-- 子组件 Child.vue -->
      <template><div>子组件</div><Grandchild v-bind="$attrs" /> <!-- 手动透传给孙组件 -->
      </template><script setup>// Vue3 中在 setup 外声明 inheritAttrsexport default {inheritAttrs: false // 根元素不再自动添加 $attrs 中的属性}
      </script>
      
    2. $attrs 中的属性是 “剩余” 未被声明的属性,遵循 “声明优先” 原则。

    3. 可以选择性透传:v-bind="{ ...$attrs, otherProp: 'value' }"(扩展运算符)。

    4. 避免过度透传$attrs 适合简单的透传场景,若透传层级过深或属性 / 事件过多,建议使用 provide/inject 或状态管理库,避免维护困难。

    七、Slots(插槽通信)

适用场景: 子组件向父组件通信

原理: 子组件在定义插槽时,通过 v-bind(或简写 :)将内部数据绑定到插槽上,这些数据会成为 “插槽作用域” 的一部分,供父组件使用。

实现步骤:

  1. 在子组件中预留插槽位置,将需要传递出去的数据通过v-bind绑定

    <template><div class="comp-child"><!-- 定义header插槽,接收title属性 --><slot name="header" :title="title"></slot></div>
    </template>
    <script setup lang="ts">
    import { ref } from 'vue'
    const title = ref<string>('懒羊羊我小弟')
    </script>
  2. 父组件通过插槽来获取子组件数据展示到页面上

    <template><div class="comp-parent"><CompChild><template #header="{ title }"><h1>{{ title }}</h1></template></CompChild></div>
    </template><script setup lang="ts">
    import { useSlots } from 'vue'
    import CompChild from './comp-child.vue'
    const slots = useSlots()
    console.log('slots: ', slots.header)
    </script>

八、Pina、VueX(状态管理库)

适用场景: 适用各种场景的组件通信

原理: 通过第三方状态管理库,去开辟不同的独立存储空间,可以处理各种场景的数据通信;

实现步骤(以Pina为例):

  1. 定义pina储存仓库

    import { defineStore } from 'pinia'
    import { ref } from 'vue'export const useUserStore = defineStore('user', () => {// Composition API 风格const user = ref<{ name: string; age: number } | null>(null)function login(userData: { name: string; age: number }) {console.log('userData: ', userData)user.value = userData}return {user,login,}
    })
  2. 在仓库中获取要展示的数据

    <!--> 子组件 <-->
    <template><div class="comp-child"><h1>{{ user?.name }}</h1></div>
    </template>
    <script setup lang="ts">
    import { storeToRefs } from 'pinia'
    // 引入 用户信息userStore
    import { useUserStore } from '@/stores/user'
    // 从userStore中获取user状态
    const userStore = useUserStore()
    // 通过storeToRefs将user状态转换为响应式引用
    const { user } = storeToRefs(userStore)
    </script>
  3. 调用方法更改仓库数据

    <!--> 父组件 <-->
    <template><div class="comp-parent"><CompChild /></div>
    </template><script setup lang="ts">
    import { useUserStore } from '@/stores/user'
    import CompChild from './comp-child.vue'
    // 引入 用户信息userStore
    const userStore = useUserStore()
    // 调用userStore的login方法,登录用户
    userStore.login({ name: '懒羊羊我小弟', age: 18 })
    </script>

九、 总结

通信方式适用场景优点缺点
Props/Emits父子组件通信简单直接,类型安全不适合深层组件
v-model表单组件双向绑定语法简洁,符合习惯仅限于表单场景
ref调用子组件方法直接访问子组件破坏组件封装性
Provide/Inject深层组件通信避免逐层传递数据流向不明确
Event Bus任意组件通信完全解耦,灵活难以调试和维护
Pinia复杂状态管理功能强大,可调试需要学习成本
Attrs属性透传灵活处理未知属性可能造成属性冲突
Slots内容分发高度灵活,可定制语法相对复杂

选择建议:

  • 简单父子通信:Props/Emits
  • 表单组件:v-model
  • 深层组件:Provide/Inject
  • 复杂应用状态:Pinia
  • 临时全局事件:Event Bus
  • UI 组件库:Slots + Attrs

根据具体场景选择合适的通信方式,保持代码的可维护性和可读性。

http://www.dtcms.com/a/477856.html

相关文章:

  • PortSwigger靶场之将 XSS 存储到onclick带有尖括号和双引号 HTML 编码以及单引号和反斜杠转义的事件中通关秘籍
  • 哪些方法可以建设网站后台网站模板下载
  • 根据PID获取K8S-Pod名称-反之POD名称获取PID
  • 做网站三年3万块钱论坛搭建一键
  • C#进阶技巧掌握外部进程的启动与控制(一):进程基础与基本操作
  • 昂瑞微:实现精准突破,攻坚射频“卡脖子”难题
  • 延安做网站的公司电话如何用云服务器搭建个人网站
  • shellSort
  • idea一直卡在build不动(Writing class)
  • LSTM自然语言处理情感分析项目(四)整合调用各类与方法形成主程序
  • MySQL为什么选择B+tree索引作为核心索引结构?
  • 在 Windows 11 上使用 JetBrains Rider 2025.2 创建 Avalonia 项目完整指南
  • 隐私保护与数据安全合规(十)
  • 【工业场景】用YOLOv8实现人员打电话识别
  • 丽水建设网站制作几年前我为客户建设网站
  • 主线程 MainLooper 和一般 Looper 的异同?
  • 【论文精读】STAR:基于文本到视频模型的空间-时间增强真实世界视频超分
  • 建设银行的积分网站百度渠道开户
  • 万网 速成网站嘉定品牌网站建设
  • Ruby on Rails 从0 开始入门到进阶到高级 - 10分钟速通版
  • Windows Docker Desktop占用C盘空间过大解决办法集合
  • 平面的方程公式
  • 2025年“羊城杯”网络安全大赛 线上初赛 (WriteUp)
  • 网络安全概念之网闸防火墙AI版
  • 学习笔记2: 深度学习之logistic回归梯度下降
  • 网络安全等级测评师能力评估样卷及答案
  • 网站服务器用什么系统网站建设及管理制度文章
  • 网站添加wordpress创意咨询策划公司
  • 企业网站设计专业好吗胶州房产网
  • 环境变量完全指南:用 Vite 把「配置」玩出花