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

Vue 中 8 种组件通信方式

vue 中常见的 8 种常规通信方案

  • 通过 props 传递
  • 通过 $emit 触发自定义事件
  • 使用 ref
  • EventBus
  • $parent 或 $root
  • attts 和 listeners
  • Provide 与 Inject
  • Vuex 和 Pinia

组件间通信的分类可以分成以下:

  • 父子关系组件选择 props 与 $emit ,或者 ref
  • 兄弟关系组件选择 $bus 和 $parent
  • 祖先与后代关系组件选择 attrs 与 listeners 或 Provide 与 Inject
  • 复杂关系组件选择 vuex 或 Pinia

1. 父子组件通信 (Props / Emit / ref)

1.1. Props(父 -> 子)

Props 是 Vue 中实现父组件向子组件传递数据的核心方式,它遵循了单向数据流的原则。

具体实现分为两步:

  • 父组件 (传递方):在模板的子组件标签上,通过属性绑定(使用 : 或 v--bind)的方式,将父组件自身的数据传递下去。
  • 子组件 (接收方):在 <script setup> 中,使用 defineProps 宏来声明并接收从父组件传递来的 props。

子组件:

<script setup lang='js'>import { ref } from 'vue'import Child from '../../components/Child.vue'const data = ref('父组件数据')
</script><template><Child :data="data" />
</template>

父组件:

<script setup lang='js'>const props = defineProps({data: {type: String,default: ''}})
</script><template><div class=''>Child {{ props.data }}</div>
</template>

1.2. emit(子 -> 父)

子组件向父组件通信,主要使用 emit 机制。首先,在子组件的 <script setup> 中,必须使用 defineEmits 来声明它将要触发的自定义事件名。然后,在需要的时候,调用 defineEmits 返回的 emit 函数,触发该事件并可以附带任意参数。在父组件中,通过在子组件标签上使用 @ 符号(即 v-on)来监听这个自定义事件,并绑定一个处理函数。当事件被触发时,这个处理函数就会被调用,并且其参数列表会按顺序接收到子组件 emit 出来的所有值,从而实现了自下而上的数据传递。

子组件:

<script setup lang='js'>const emit = defineEmits(['custom-event'])const handleClick = () => {emit('custom-event', '子组件传值')}
</script><template><button @click="handleClick">点击传值</button>
</template>

父组件:

<script setup lang='js'>import Child from '../../components/Child.vue'const handleCustomEvent = (value) => {console.log(value)}
</script><template><Child :data="data" @custom-event="handleCustomEvent" />
</template>

1.3. Ref

1.3.1. 父 -> 子

通过 ref 实现父组件向子组件的命令式通信:首先,在父组件的模板中,为子组件标签添加 ref 属性以获取其实例;然后,在父组件的逻辑中,通过访问这个 ref 实例直接调用子组件暴露的方法,并将需要传递的数据作为方法参数传入。子组件对应的方法在接收到参数后,即可执行相应操作。

父组件:

<script setup lang='js'>import { ref } from 'vue'import Child from '../../components/Child.vue'const data = ref('父组件传递的数据')const ChildRef = ref(null)const handleChild = () => {ChildRef.value.init(data)}
</script><template><div class=''><Child ref="ChildRef" /><button @click="handleChild">传递数据</button></div>
</template>

子组件:

<script setup lang='js'>import { ref } from 'vue'import GrandChild from './GrandChild.vue'let data = ref('')const init = (value) => {data.value = valueconsole.log(value)}defineExpose({ init })
</script><template><div class=''>Child<GrandChild /></div>
</template>
1.3.2. 子 -> 父

在 Vue 3 中,父组件可以通过 ref 来获取子组件的实例,并主动调用子组件暴露的方法或访问其数据。

这个过程分为两步:

  • 子组件(被动方):在 <script setup> 中,子组件必须使用 defineExpose 宏,显式地将其希望被父组件访问的属性和方法暴露出去,形成一个公开的 API。
  • 父组件(主动方):在父组件模板中,通过在子组件标签上设置 ref="childRef",并将 childRef 关联到一个 ref() 对象,即可获取到子组件的实例。之后,父组件就可以在适当的时机(如 onMounted 或事件回调中),通过 childRef.value 来访问子组件暴露的 API

子组件:

<script setup lang='js'>import { ref } from 'vue'import GrandChild from './GrandChild.vue'let ChildData = ref('子组件的数据')defineExpose({ ChildData })
</script><template><div class=''>Child<GrandChild /></div>
</template>

父组件:

<script setup lang='js'>import { onMounted } from 'vue'import Child from '../../components/Child.vue'onMounted(() => {if (ChildRef.value) {console.log(ChildRef.value.ChildData);}})
</script><template><div class=''><Child ref="ChildRef" /></div>
</template>

2. 祖孙/跨级组件通信 (Provide / Inject)

provide 和 inject 是 Vue 官方推荐的跨级组件通信方案。它允许一个祖先组件 (Provider) 作为依赖的提供者,将其数据或方法提供 (provide) 给其后代组件树中的任何一个组件。

最核心的优势在于,这个过程具有“穿透性”。无论组件层级有多深,任何中间层组件都无需进行任何额外的 props 声明或传递。最终的后代组件 (Injector) 只需要通过 inject 并使用与提供者相同的 key,就能精准地接收到这份依赖。

简单来说:祖先组件使用 provide 给孙子传递组件,中间层什么都不需要做,在孙子组件使用 inject 接收

祖先组件:

<script setup lang='js'>import { ref, provide, readonly } from 'vue'import Child from '../../components/Child.vue'const data = ref('祖先数据')provide('data', data) // readonly(data) 使用 readonly 包裹代表只读,孙子不可修改</script><template><div class=''><Child /></div>
</template>

孩子(中间层):

<script setup lang='js'>import GrandChild from './GrandChild.vue'
</script><template><div class=''>Child  <GrandChild /></div>
</template>

孙子组件:

<script setup lang='js'>import { inject } from 'vue';let data = inject('data');console.log("祖先传递过来的数据:", data.value);</script><template><div class=''>{{ data }}</div>
</template>

3. 祖孙组件通信($attrs 与 listeners)

孙子向祖先通信,是通过事件透传实现的:祖先在父组件上监听事件,父组件通过 v-bind="$attrs" 将该监听器透传给孙子,最终由孙子组件 emit 触发。

祖先向孙子传值,是一个自上而下的数据流:祖先传递的属性,父组件通过 v-bind="$attrs",最终被孙子组件的 props 接收,完美解决了“属性逐层传递”的问题。

祖先组件:

<script setup lang='js'>import { ref, onMounted } from 'vue'import Child from '../../components/Child.vue'// 准备要传递的数据const messageFromGrand = ref('来自祖先的问候!');const userFromGrand = ref({ name: '张三' });// 准备一个方法来处理来自孙子组件的响应const handleChildResponse = (dataFromChild) => {console.log('祖先组件收到了来自孙子的响应:', dataFromChild);console.log(`祖先收到了来自 ${dataFromChild.from} 的消息: ${dataFromChild.text}`);};</script><template><div class="grandparent-component"><h1>我是祖先组件 (Grandparent)</h1><Child parentTitle="父组件的专属标题" :message="messageFromGrand" :user="userFromGrand" @response="handleChildResponse" /></div>
</template><style scoped>.grandparent-component {border: 2px solid #3498db;padding: 20px;}
</style>

父组件:

<script setup lang='js'>
import { ref } from 'vue'
import GrandChild from './GrandChild.vue'// 1. 【关键】Parent 组件只声明它自己真正关心的 prop
const props = defineProps({parentTitle: {type: String,required: true}
});</script><template><div class='parent-component'><h2>我是父组件 (Parent)</h2><p>我只关心我自己的标题: <strong>{{ props.parentTitle }}</strong></p><p>其他来自祖先的东西,我直接透传给 Child...</p><!-- 2. 【核心】使用 v-bind="$attrs"$attrs 对象包含了所有从 Grandparent 传递过来,但没有被 Parent 的 props(即 parentTitle) 接收的“剩余”属性和事件。v-bind="$attrs" 会将这些属性和事件监听器,原封不动地应用到 Child 组件上。--><GrandChild v-bind="$attrs" /></div>
</template><style scoped>
.parent-component {border: 2px solid #e67e22;padding: 20px;margin-top: 20px;
}
</style>

孙子组件:

<script setup>
// 1. 声明接收来自祖先的 props
const props = defineProps({message: {type: String,default: '默认消息'},user: {type: Object,default: () => ({ name: '默认用户' })}
});// 2. 声明会触发的事件
const emit = defineEmits(['response']);const handleChildClick = () => {// 3. 触发事件,并回传数据const responseData = { from: 'Child', text: '你好,祖先!' };emit('response', responseData);
};
</script><template><div class="child-component"><h3>我是孙子组件 (Child)</h3><p>接收到的消息: <strong>{{ props.message }}</strong></p><p>接收到的用户: <strong>{{ props.user.name }}</strong></p><button @click="handleChildClick">点击我,向祖先响应</button></div>
</template><style scoped>
.child-component {border: 2px solid #42b983;padding: 20px;margin-top: 20px;
}
</style>

4. EventsBus

main.js

import Vue from 'vue'
Vue.prototype.$bus = new Vue()

发送:

this.$bus.$emit('custom-event', 'hello')

接收:

this.$bus.$on('custom-event', msg => {console.log('接收到:', msg)
})

5. root

传递方:

<template><Child />
</template><script setup>import { getCurrentInstance } from 'vue'import Child from '../../components/Child.vue'const app = getCurrentInstance()app.appContext.config.globalProperties.$rootMsg = "来自 $root 的全局信息"
</script>

接收方:

<template><div><p>孙组件获取的 root 信息:{{ rootMsg }}</p></div>
</template><script setup>
import { getCurrentInstance } from 'vue'const app = getCurrentInstance()
const rootMsg = app.appContext.config.globalProperties.$rootMsg
console.log(rootMsg);
</script>
http://www.dtcms.com/a/392391.html

相关文章:

  • 十三、vue3后台项目系列——sidebar的实现,递归组件
  • LeetCode 383 - 赎金信
  • compose multiplatform reader3
  • Redis 入门与实践
  • 【OpenGL】texture 纹理
  • agentscope以STUDIO方式调用MCP服务
  • 无公网 IP 访问群晖 NAS:神卓 N600 的安全解决方案(附其他方法风险对比)
  • Redis最佳实践——性能优化技巧之Pipeline 批量操作
  • Java-130 深入浅出 MySQL MyCat 深入解析 核心配置文件 server.xml 使用与优化
  • 业主信息查询功能测试指南
  • WinDivert学习文档之四-————卸载
  • 分布式链路追踪关键指标实战:精准定位服务调用 “慢节点” 全指南(二)
  • DuckDB客户端API之ADBC官方文档翻译
  • 区块链技术应用开发:智能合约进阶与多链生态落地实践
  • 分布式专题——13 RabbitMQ之高级功能
  • 神经风格迁移(Neural Style Transfer)
  • Chromium 138 编译指南 Ubuntu 篇:源码获取与版本管理(四)
  • R 语言入门实战|第九章 循环与模拟:用自动化任务解锁数据科学概率思维
  • [论文阅读] 人工智能 + 软件工程 | 4907个用户故事验证!SEEAgent:解决敏捷估计“黑箱+不协作”的终极方案
  • 鸿蒙Next ArkTS卡片开发指南:从入门到实战
  • 【绕过disable_function】
  • 使用云手机运行手游的注意事项有哪些?
  • 【数据结构】利用堆解决 TopK 问题
  • 2025陇剑杯现场检测
  • openharmony之充电空闲状态定制开发
  • 【开题答辩全过程】以 python的线上订餐系统为例,包含答辩的问题和答案
  • (附源码)基于Spring Boot的校园心理健康服务平台的设计与实现
  • 微信小程序开发教程(十八)
  • 寰宇光锥舟架构图
  • Spring Bean生命周期全面解析