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

Vue3---(9)组件间通信

目录

Props

 自定义事件

mitt

 v-model

$attrs

$refs、$parent

provide、inject

Pinia

插槽

默认插槽

具名插槽

 作用域插槽

面试重点

为什么不能直接修改 props?如何正确修改?

自定义事件机制原理

自定义事件和全局事件总线如何选择?

 v-model 和 .sync 的区别?

如何处理 v-model 的性能问题?

v-model 在自定义组件中的完整实现流程?​

$attrs 与 props 的区别?

$attrs 包含哪些内容?​ 

为什么说 $parent 是反模式?

如何安全地使用 $refs?​ 

provide/inject 与 Vuex/Pinia 的区别?​

作用域插槽的实现原理?​

Props

使用频率最高的组件间通信方式,一般用来父子组件间进行通信(注意遵循单项数据流原则)

  • 父→子:属性值为非函数,直接传值
  • 子→父:属性值为函数,子组件通过调用父组件方法传递参数 

父组件 

<template><div class="father"><h3>父组件,</h3><h4>父亲的车:{{ car }}</h4><h4>儿子给的玩具:{{ toy }}</h4>// 父组件通过props直接将属性值传给子组件<Child :car="car" :getToy="getToy"/></div>
</template><script setup lang="ts" name="Father">import Child from './Child.vue'import { ref } from "vue";// 数据const car = ref('奔驰')const toy = ref()// 子组件调用父组件的函数,通过函数来接受子组件传递的参数function getToy(value:string){toy.value = value}
</script>

子组件

<template><div class="child"><h3>子组件</h3><h4>我的玩具:{{ toy }}</h4><h4>父给我的车:{{ car }}</h4>//子组件通过调用父组件的函数,将数据传递给父组件的函数中,实现子传父<button @click="getToy(toy)">玩具给父亲</button></div>
</template><script setup lang="ts" name="Child">import { ref } from "vue";const toy = ref('奥特曼')//子组件通过defineProps 接收父组件传递的数据defineProps(['car','getToy'])
</script>

 props的详细使用可见之前的文章:props的详细使用

 自定义事件

自定义事件常用于子组件向父组件传递,子组件通过触发自定义事件,将数据传递给父组件

父组件 

<template><div class="father"><h3>父组件</h3><h4 v-show="toy">子给的玩具:{{ toy }}</h4><!-- 给子组件Child绑定自定义事件 --><Child @send-toy="saveToy"/></div>
</template><script setup lang="ts" name="Father">import Child from './Child.vue'import { ref } from "vue";// 数据let toy = ref('')// 用于保存传递过来的玩具,接收子组件调用自定义事件传递的参数function saveToy(value:string){console.log('saveToy',value)toy.value = value}
</script>

子组件 

<template><div class="child"><h3>子组件</h3><h4>玩具:{{ toy }}</h4>//触发调用自定义事件,并把toy传递给父组件<button @click="emit('send-toy',toy)">测试</button></div>
</template><script setup lang="ts" name="Child">import { ref } from "vue";// 数据let toy = ref('奥特曼')// 接收父组件传递的自定义事件send-toyconst emit =  defineEmits(['send-toy'])
</script>

mitt

 与消息订阅与发布(pubsub)功能类似,可以实现任意组件间通信。

安装

npm i mitt

 引入创建src/utils/mitter.ts

// 引入mitt 
import mitt from "mitt";
// 创建emitter
const emitter = mitt()// 绑定事件emitter.on('abc',(value)=>{console.log('abc事件被触发',value)})setInterval(() => {// 触发事件emitter.emit('abc',666)}, 1000);setTimeout(() => {// 清理事件emitter.all.clear()}, 3000); 
// 创建并暴露mitt
export default emitter

 使用注意:要在组件销毁前解绑事件

事件监听: 

import emitter from "@/utils/emitter";
import { onUnmounted } from "vue";// 绑定事件
emitter.on('send-toy',(value)=>{console.log('send-toy事件被触发',value)
})onUnmounted(()=>{// 解绑事件emitter.off('send-toy')
})

事件触发:

import emitter from "@/utils/emitter";function sendToy(){// 触发事件emitter.emit('send-toy',toy.value)
}

 v-model

v-model是Vue的双向绑定语法糖,本质=属性绑定+事件监听的封装

<!-- 基础表单元素 -->
<input v-model="searchText">
<!-- 等价于 -->
当input数据发生改变的时候触发input事件,将改变的值赋值给userName,然后将userName的值用来展示数据
<input type="text" :value="userName" @input="userName =(<HTMLInputElement>$event.target).value"
><!-- 自定义组件 -->
<CustomInput v-model="message" />
<!-- 等价于 -->
// 同样的自定义组件的值发生改变时,触发update:modelValue
<CustomInput :modelValue="message" @update:modelValue="message = $event"
>

$attrs

用于透传未声明的props的特性集合,用于实现当前组件的父组件,向当前组件的子组件通信(祖→孙

 $attrs是一个对象,包含所有父组件传入的标签属性。($attrs会自动排除props中声明的属性 )

父组件

<template><div class="father"><h3>父组件</h3><Child :a="a" :b="b" :c="c" :d="d" v-bind="{x:100,y:200}" :updateA="updateA"/></div>
</template><script setup lang="ts" name="Father">import Child from './Child.vue'import { ref } from "vue";let a = ref(1)let b = ref(2)let c = ref(3)let d = ref(4)function updateA(value){a.value = value}
</script>

 子组件

<template><div class="child"><h3>子组件</h3><GrandChild v-bind="$attrs"/></div>
</template><script setup lang="ts" name="Child">import GrandChild from './GrandChild.vue'
</script>

 祖组件

<template><div class="grand-child"><h3>孙组件</h3><h4>a:{{ a }}</h4><h4>b:{{ b }}</h4><h4>c:{{ c }}</h4><h4>d:{{ d }}</h4><h4>x:{{ x }}</h4><h4>y:{{ y }}</h4><button @click="updateA(666)">点我更新A</button></div>
</template><script setup lang="ts" name="GrandChild">defineProps(['a','b','c','d','x','y','updateA'])
</script>

$refs、$parent

  • $refs用于 :父→子。

  • $parent用于:子→父。

特性$refs$parent
​作用对象​子组件/DOM 元素父组件实例
​数据流向​父组件操作子组件子组件访问父组件
​响应式​非响应式非响应式
​使用场景​需要直接操作子组件/DOM需要访问父组件方法/数据(不推荐)
​Vue3 变化​支持 ref() 组合式 API多根组件中返回首个父组件
​企业级规范​谨慎使用,避免过度依赖几乎禁止使用,推荐替代方案

 父组件

<template><div class="father"><h3>父组件</h3><h4>房产:{{ house }}</h4><button @click="changeToy">修改Child1的玩具</button><button @click="changeComputer">修改Child2的电脑</button><button @click="getAllChild($refs)">让所有孩子的书变多</button><Child1 ref="c1"/><Child2 ref="c2"/></div>
</template><script setup lang="ts" name="Father">import Child1 from './Child1.vue'import Child2 from './Child2.vue'import { ref,reactive } from "vue";//接收reflet c1 = ref()let c2 = ref()// 数据let house = ref(4)// 通过ref直接改变子组件的值function changeToy(){c1.value.toy = '小猪佩奇'}function changeComputer(){c2.value.computer = '华为'}function getAllChild(refs:{[key:string]:any}){console.log(refs)for (let key in refs){refs[key].book += 3}}// 向外部提供数据(只有暴露以后,子组件才能通过$parent来访问到)defineExpose({house})
</script>

子组件

<template><div class="child1"><h3>子组件1</h3><h4>玩具:{{ toy }}</h4><h4>书籍:{{ book }} 本</h4><button @click="minusHouse($parent)">干掉父亲的一套房产</button></div>
</template>
<script setup lang="ts" name="Child1">import { ref } from "vue";// 数据let toy = ref('奥特曼')let book = ref(3)// 方法function minusHouse(parent:any){parent.house -= 1}// 把数据交给外部defineExpose({toy,book})</script>

provide、inject

实现祖孙组件间直接通信,解决深层嵌套组件间的​​prop逐层传递​​问题。

provide() 接受两个参数:第一个参数是要注入的 key,可以是一个字符串或者一个 symbol,第二个参数是要注入的值。

inject()接受两个参数: 第一个参数是注入的 key,第二个参数是可选的,即在没有匹配到 key 时使用的默认值。

  • 在祖先组件中通过provide配置向后代组件提供数据

  • 在后代组件中通过inject配置来声明接收数据

父组件中,使用provide提供数据  

<template><div class="father"><h3>父组件</h3><h4>资产:{{ money }}</h4><h4>汽车:{{ car }}</h4><button @click="money += 1">资产+1</button><button @click="car.price += 1">汽车价格+1</button><Child/></div>
</template><script setup lang="ts" name="Father">import Child from './Child.vue'import { ref,reactive,provide } from "vue";// 数据let money = ref(100)let car = reactive({brand:'奔驰',price:100})// 用于更新money的方法function updateMoney(value:number){money.value += value}// 提供数据provide('moneyContext',{money,updateMoney})provide('car',car)
</script>

孙组件中使用inject配置项接受数据

<template><div class="grand-child"><h3>我是孙组件</h3><h4>资产:{{ money }}</h4><h4>汽车:{{ car }}</h4><button @click="updateMoney(6)">点我</button></div>
</template><script setup lang="ts" name="GrandChild">import { inject } from 'vue';// 注入数据let {money,updateMoney} = inject('moneyContext',{money:0,updateMoney:(x:number)=>{}})let car = inject('car')
</script>

Pinia

具体使用见之前文章:Pinia的详细使用

插槽

默认插槽

父组件中:<Category title="今日热门游戏"><ul><li v-for="g in games" :key="g.id">{{ g.name }}</li></ul></Category>
子组件中:<template><div class="item"><h3>{{ title }}</h3><!-- 默认插槽 --><slot></slot></div></template>

具名插槽

父组件中:<Category title="今日热门游戏"><template v-slot:s1><ul><li v-for="g in games" :key="g.id">{{ g.name }}</li></ul></template><template #s2><a href="">更多</a></template></Category>
子组件中:<template><div class="item"><h3>{{ title }}</h3><slot name="s1"></slot><slot name="s2"></slot></div></template>

 作用域插槽

父组件中:<Game v-slot="params"><!-- <Game v-slot:default="params"> --><!-- <Game #default="params"> --><ul><li v-for="g in params.games" :key="g.id">{{ g.name }}</li></ul></Game>子组件中:<template><div class="category"><h2>今日游戏榜单</h2><slot :games="games" a="哈哈"></slot></div></template><script setup lang="ts" name="Category">import {reactive} from 'vue'let games = reactive([{id:'asgdytsa01',name:'英雄联盟'},{id:'asgdytsa02',name:'王者荣耀'},{id:'asgdytsa03',name:'红色警戒'},{id:'asgdytsa04',name:'斗罗大陆'}])</script>

面试重点

为什么不能直接修改 props?如何正确修改?

Props 遵循单向数据流原则,直接修改会导致数据流向不清晰,子组件应通过 $emit 触发事件让父组件修改。

自定义事件机制原理

  1. 子组件调用 $emit('event-name', payload)
  2. 父组件通过 @event-name="handler" 监听
  3. Vue 内部维护事件监听器映射表

自定义事件和全局事件总线如何选择?

  • 自定义事件:父子组件直接通信
  • 事件总线:跨组件/兄弟组件通信
  • 复杂场景建议使用 Pinia/Vuex

 v-model 和 .sync 的区别?

  • ​Vue2​​:
    <!-- .sync 实现多个双向绑定 -->
    <Child :title.sync="pageTitle" :content.sync="pageContent" />
  • ​Vue3​​:.sync 被废弃,用 v-model:arg 替代实现多个双向绑定
<UserForm v-model:name="name" v-model:age="age" />

如何处理 v-model 的性能问题?

  • 避免深层对象绑定
  • 使用 computed 做代理
  • 对大型列表使用虚拟滚动

v-model 在自定义组件中的完整实现流程?​

  1. 定义 props 接收 modelValue
  2. 定义 emits 声明 update:modelValue
  3. 在需要时调用 emit('update:modelValue', newValue)

$attrs 与 props 的区别?

  • ​​props​​: 显式声明的接收属性
  • ​​$attrs​​: 父组件传递但未声明的剩余属性

$attrs 包含哪些内容?​ 

  • 所有未被声明为 props 的:

    • HTML 属性(idplaceholder 等)

    • 自定义事件(@custom-event

    • class 和 style(Vue3 新增)

为什么说 $parent 是反模式?

  • ​​破坏封装性​​:子组件直接依赖父组件实现细节

  • ​​降低复用性​​:组件无法独立于特定父组件使用

  • ​​维护困难​​:组件层级变化会导致连锁问题

如何安全地使用 $refs?​ 

  • ​​生命周期控制​​:确保在 mounted 后访问

  • ​​类型检查​​:添加必要的空值判断

  • ​​文档注释​​:明确 ref 的用途和接口

provide/inject 与 Vuex/Pinia 的区别?​

  • ​​定位不同​​:
    • provide/inject 是组件级状态共享
    • Vuex/Pinia 是全局状态管理
  • ​​适用场景​​:
    • 跨层级组件通信 → provide/inject
    • 复杂应用全局状态 → Vuex/Pinia

作用域插槽的实现原理?​

  • 子组件将数据通过函数参数传递
  • 父组件接收数据并生成渲染函数
  • 运行时子组件调用该函数并传入数据

相关文章:

  • 【基于WSAAsyncSelec模型的通信程序设计】
  • 每天学一个 Linux 命令(29):tail
  • JavaScript 中的单例模式
  • 单例模式(线程安全)
  • Uniapp 自定义TabBar + 动态菜单实现教程(Vuex状态管理详解)
  • Nginx详细使用
  • 乐家桌面安卓版2025下载-乐家桌面软件纯净版安装分享码大全
  • buildadmin 自定义单元格渲染
  • AOSP Android14 Launcher3——点击桌面图标启动应用动画流程
  • Docker安装beef-xss
  • Python3网络爬虫开发--爬虫基础
  • 多语言商城系统开发流程
  • 【Easylive】consumes = MediaType.MULTIPART_FORM_DATA_VALUE 与 @RequestPart
  • 【英语语法】词法---连词
  • Spring是如何实现资源文件的加载
  • LX5-STM32F103C8T6引脚分布与定义
  • longchain使用通义千问
  • 如何对只能有一个`public`顶层类这句话的理解
  • 大文件分片上传进阶版(新增md5校验、上传进度展示、并行控制,智能分片、加密上传、断点续传、自动重试),实现四位一体的网络感知型大文件传输系统‌
  • Maxscript调用Newtonsoft.Json解析Json
  • 高架上2名儿童从轿车天窗探出身来,驾驶员被记3分罚200元
  • 扫描类软件成泄密“推手”,网盘账号密码遭暴力破解
  • GDP增长6.0%,一季度浙江经济数据出炉
  • 运油-20亮相中埃空军联训
  • 江南大部、江淮南部等地今起有较强降雨,水利部部署防范工作
  • 解除近70家煤电厂有毒物质排放限制,特朗普能重振煤炭吗?