Vue 组件通信

基本概念

Vue 作为一个渐进式 JavaScript 框架,其核心设计之一就是组件化,它通过提供多种隔离手段来确保组件的独立性和封装性(如CSS样式隔离的 '<style scoped>' ,数据隔离data函数),增强代码的复用性、可维护性增强、开发效率提升。

但随着项目复杂度的增加,组件与组件之间不可避免地需要进行数据交互。为了在实现目的的同时,减少组件之间的直接依赖和耦合,vue提供了许多组件通信的方式。


vue 2组件通信

传递响应式数据提供的一个可观察的对象(如用Vue.observable()创建的对象)

1.props

props通信方法可以实现父组件向子组件传递参数,也可以实现子组件给父组件传递参数,是使用频率最高的

1)父传子:属性值非函数

父组件,引入子组件并给子组件绑定一个message参数,用以传递父组件的数据

子组件,使用props()接收

代码:

//父组件传递
<ChildA :message="parent_msg"/>

//子组件接收
export default {
//数组写法
  props: ["message"]
//对象写法
  props :{
    message: {
      type: String,
      required: true, //是否规定必须得有
      default: '默认值'//默认数据,当父组件没有传递数据时,读取该数据
    }
  }
}

2)子传父:属性值为函数(可携带值)

 父组件,引入子组件并给子组件绑定一个sendMsg参数(可以携带参数的函数)

子组件,使用props()接收,接收到函数后,可用来向父组件传递自身数据

代码:

//父组件绑定带值函数
<ChildA :sendMsg="receive"/>

receive(value){
   console.log(value)//子组件数据
}
//子组件接收传递
<button @click="sendMsg('子组件数据')">传递</button>

props: ["sendMsg"]
//或
props:{
    sendMsg:{
        type:Function,
        default:()=>{}
    }
}

2.$emit

自定义事件emits,常用于子传父

父组件:引入子组件,并监听子组件的自定义事件sendMsg的触发,当事件被触发时,执行方法receive来处理接收到的数据

子组件:使用this.$emit声明一个自定义事件sendMsg,调用tirggerFun方法时触发该事件并向外传递数据child_msg

代码:

//父组件监听事件触发方法
<ChildA @sendMsg="receive"/>
receive(value){
  console.log(value)//子组件数据
}
//子组件,触发事件携带值
<button @click="triggerFun">传递</button>
triggerFun(){
  this.$emit('sendMsg','子组件数据')
}

3.provide和inject

可以实现上层组件对于后代组件的值传递,相当于 “ 穿透传递 ”

祖组件,引入子组件,使用provide向后代组件提供数据。

后代组件,使用inject接收数据。

注意:provide是一个对象,inject是一个数组。

代码:

//祖组件
provide:{
  parent_msg:'parent组件的信息'
}
//后代组件
inject:['parent_msg']

4.$refs

$refs是一个包含了所有带有 ref 属性的子组件或 DOM 元素的引用的对象

在父组件中通过$refs获取到子组件实例对象的引用后。可利用函数可以携带值得特性,由父组件向子组件传递数据,也可以由父组件主动获取子组件的数据(getter)

父传子

父组件:引入子组件,并在组件上为其声明一个ref属性childRef。通过this.$refs.childRef.[方法/属性]来访问子组件中的属性方法。

子组件:定义用于传递或接收数据的方法。如getValue

代码:

//父组件
<ChildA ref="childRef"/>

mounted() {
  this.$refs.childRef.getValue('父组件数据')
}
//子组件
getValue(value){
  console.log(value)
}

 5.$parent和$children

1)$parent

$parent:获取父组件实例对象,包含父节点中所有的数据和方法等

父组件:引入子组件

子组件:通过this.$parent.[方法/属性]来访问父组件中的属性方法。

代码:

//父组件
...属性方法

//子组件
console.log(this.$parent.parent_msg)//访问父组件属性
this.$parent.testFun()//调用父组件方法

2).$children

 $children:获取一个包含所有子组件(不包含孙子组件)的组件实例 对象数组,可以直接拿到子组件中所有数据和方法等 

 父组件:引入子组件,通过this.$children[index].[方法/属性]来访问子组件中的属性方法

//父组件
console.log(this.$children[0].child_msg)//访问第一个子组件的属性
this.$children[0].testFun()//调用第一个子组件的方法

//子组件
...属性方法


6.$attrs和$listeners

先理解:一个非 prop 的 attribute 是指传向一个组件,但是该组件并没有相应 prop 定义的 attribute。

//父组件
<ChildA :parent_msg="parent_msg" :other="othre"/>
//子组件
props:['parent_msg']

比如:这里的other就是非 prop 的 attribute

1)$attrs

$attrs:是一个包含父作用域里(除 class 和 style 之外)的非 props 属性集合可以在模板的表达式中直接用 $attrs 访问这些非 prop 的 attribute

父组件:引入子组件,并在组件上绑定参数。如parent_msg和other_msg

子组件:直接在template中使用$attrs访问非props数据,在script中使用this.$attrs访问

代码: 

//父组件
<ChildA :parent_msg="parent_msg" :other="other"/>

//子组件

1.在template中访问
{{ $attrs }}

2.在script中访问
console.log(this.$attrs)

3.通过v-bind直接传给下一级子组件:比如在子组件Child中给孙组件GrandSon绑定$atter
<GrandSon v-bind="$attrs"></GrandSon>

2)$listeners

$listeners:是一个包含父作用域里 .native 除外的监听事件集合

 

父组件:引入子组件,并在组件上监听事件。如click和enter

子组件:在script中通过访问this.$listeners访问父组件中未被.native修饰的事件。可通过$emit来触发

代码:

//父组件
<ChildA @click="clickFun" @enter.native="enterFun"/>

//子组件
1.script中直接访问
console.log(this.$listeners) //{click: f}
this.$emit('click')//触发parent的clickFun


2.通过v-on直接传给下一级子组件:比如在子组件Child中监听孙组件GrandSon的事件
<GrandSon v-on="$listeners"></GrandSon>

7.v - model

父组件,引入子组件,并使用v-model指令为其绑定message参数用以数据的双向绑定

子组件,使用props()接收message;使用$emit触发mole事件修改父组件中的message

代码:

//父组件
<ChildA v-model="message"></ChildA>

</script>
//子组件
<input :value="message" @input="handlerChange" />

props: ["message"],
model:{ // 可以修改事件名,默认为 input
  event:'update'
},
methods: {
  handlerChange(e) {
   this.$emit("update", e.target.value);
}

8.Bus

在 Vue 2 中,全局事件总线是一种在组件之间通信的方式,尤其是当组件结构较深,而你又不想通过多层组件的 props 和 events 传递数据时。全局事件总线通常是一个空的 Vue 实例,用于触发和监听事件。这样,任何组件都可以通过这个实例来发送或接收事件,实现跨组件通信。

1).在main.js文件中配置

// 创建一个空的 Vue 实例作为全局事件总线  
const EventBus = new Vue();  
  
// 将 EventBus 添加到 Vue 的原型上,这样你就可以在任何组件中通过 this.$eventBus 访问它  
Vue.prototype.$eventBus = EventBus;  

// Vue 应用实例  
new Vue({  
  // ...  
});

2).发送事件

在任意组件中,可以使用 $emit 方法来触发一个事件,并通过第二个参数传递数据。

// 在某个组件中  
this.$eventBus.$emit('eventName', { message: 'Hello from Component A!' });

3).监听事件

在另一个组件中,可以使用 $on 方法来监听这个事件。同时,还需要在组件销毁前使用 $off 方法来移除监听器,以避免潜在的内存泄漏。

// 在另一个组件中  
created() {  
  // 监听事件  
  this.$eventBus.$on('eventName', this.handleEvent);  
},  
beforeDestroy() {  
  // 移除监听器  
  this.$eventBus.$off('eventName', this.handleEvent);  
},  
methods: {  
  handleEvent(data) {  
    console.log(data.message); // 输出: Hello from Component A!  
  }  
}

示例:

父组件,在Parent组件中引入ChildA,ChildB

子组件,在ChildA中注册监听事件,在ChildB中中触发ChildA注册的监听事件来实现数据通信

代码:

//Parent
....引入子组件

//ChildA
this.$bus.$on("testFun", (value) => {
     console.log(value)
})

//ChildB
<button @click="triggerFun">触发</button>

triggerFun(){
     this.$bus.$emit("testFun", '实现任意组件通信')
}

9.vuex 

推荐参考:Vue状态管理工具:vuex

可创建一个专用于组件通信的模块


vue 3组件通信

以下示例皆以vue 3 Composition API 的 <script setup> 语法糖为基础(传递响应式数据使用ref,reactive声明的对象)

1.props

1)父传子:属性值非函数

父组件,引入子组件,并给其绑定一个参数

子组件,使用defineProps()接收

代码:

//父组件
<Child :message="info"></Child>

//子组件
const props = defineProps(['message'])
//或
const props = defineProps({
  msg: {
    type: String,
    required: true, //是否规定必须得有
    default: '默认值'//默认数据,当父组件没有传递数据时,读取该数据
  }
}

defineProps 是 Vue 3 中 <script setup> 语法糖提供的一个编译时宏,用于在 <script setup> 语法糖中显式地声明组件接收的 props。

2)子传父:属性值为函数(可携带值)

父组件,引入子组件,并给其绑定一个参数(可以携带数据的函数)

子组件,使用defineProps()接收,在接收到父组件传递的函数后,可用来向父组件传递自身数据

代码:略


2.$emit

自定义事件emits,常用于子传父

父组件:引入子组件,并监听子组件的自定义事件sendMsg的触发,当事件被触发时,执行方法receive来处理接收到的数据

子组件:使用defineEmits声明一个可以传递数据的自定义事件getMsg,用以向父组件传递数据

代码:

//父组件
<Child @sendMsg="receive" />

function receive(value) {
  console.log(value)
}

//子组件
<button @click="triggerFun()">传递</button>

const emits = defineEmits(['sendMsg'])

function triggerFun() {
  emits('sendMsg', '子组件数据')
}

defineEmits是 Vue 3 中 <script setup> 语法糖提供的一个编译时宏,用于声明组件可以触发的自定义事件。当使用defineEmits时,实际上是在告诉Vue:“这个组件可能会触发这些事件,请帮我设置好内部机制以便我可以从组件内部触发它们。” 


3.provide和inject

1)祖辈向后代传递数据

祖组件,从vue中引入provide ,而后使用provide向后代提供数据。provide('名字',值)     

后代组件,从vue中引入inject,而后使用inject接收数据。inject('名字','默认值')

代码:

//parent组件
import { provide } from 'vue'

let parent_msg = 'parent的数据'
provide('dataName', parent_msg)

//grandson组件
import { inject } from 'vue'

const data = inject('dataName', '默认值')

2)后代向祖辈传递数据

祖组件,从vue中引入provide ,使用provide向后代提供可携带值得方法。provide('名字',方法)     

后代组件,从vue中引入inject,使用inject接收方法,收到之后使用该方法携带数据向祖组件传递。inject('名字','默认方法')

代码:略


4.expose和ref

可以在父组件中获取到子组件的实例 。同vue2,可利用函数可以携带值得特性,由父组件向子组件传递数据,也可以由父组件主动获取子组件的数据(getter)

父组件主动获取子组件数据

父组件,引入子组件并给其设置ref属性,并声明一个与其同名的ref变量,通过[变量名].value来获取子组件的实例对象,访问子组件向外暴露出的属性和方法

子组件,通过defineExpose()控制子组件内部哪些内容可以通过模板引用被外部访问

代码:

//parent组件
<Child ref="childRef" />

import { ref, onMounted } from 'vue'

const childRef = ref(null)
onMounted(() => {
  console.log(childRef.value.child_msg)
})

//child组件
let child_msg = 'child的数据'

defineExpose({
  child_msg
})

defineExpose 是 Vue 3 中 <script setup> 语法糖提供的一个编译时宏,它用于显式地暴露组件内部的属性、方法或其他响应式状态给其父组件或外部使用


5.$parent

可以在子组件内部获取到父组件的实例 

父组件,通过defineExpose({})控制父组件内部哪些内容可以通过模板引用被外部访问

子组件,在调用方法时将$parent作为参数传入其中,可在方法内部访问父组件暴露的属性和方法

代码:

//parent组件
let parent_msg = 'parent的数据'
defineExpose({
  parent_msg
})

//child组件
<button @click="triggerFun($parent)">触发</button>

const triggerFun = (parentItem) => {
  console.log(parentItem.parent_msg)
}

vue 2中的  $children,由于违背了单向数据流,官方在vue 3中已经删除


 6.$attrs 

首先理解透传:官方的解释是,“透传 attribute”指的是传递给一个组件,却没有被该组件声明为 props 或 emits 的 attribute 或者 v-on 事件监听器。

透传进子组件的 attribute 可以在模板的表达式中直接用 $attrs(一个组件实例上的属性 访问到。 $attrs 对象包含了除组件所声明的 props 和 emits 之外的所有其他 attribute,例如 classstylev-on 监听器等等。

和vue 2不同的是,vue 3中$listeners 属性被移除,所有的事件监听器现在都包含在 $attrs 中 

父组件,引入子组件并给子组件绑定参数,监听事件。用以传递数据

子组件,通过$attrs 对象访问透传attribute

代码:

//parent组件
<Child :msg="parent_msg" :other="other" @click="testFun" />

let parent_msg = 'parent的数据'
let other = '其他'
const testFun = () => {
  console.log('触发parent的testFun')
}

//child组件
//templa模板中
<p>{{ $attrs }}</p>

//script标签中
import { useAttrs } from 'vue'
defineProps(['msg'])

//直接访问attrs对象
const attrs = useAttrs()
console.log(attrs) //{other: '其他', onClick: ƒ}

更多详细内容,请查看官方文档 透传 Attributes | Vue.js


7.v - model

在组件通信中可用于实现父子组件数据同步

父组件,引入子组件,并使用v-model指令为其绑定modelValue参数用以数据的双向绑定

子组件,使用defineProps()接收message数据,使用defineEmits()定义事件update:modelValue,

详细描述:

“update:modelValue ”和 “modelValue” 是Vue为组件上的v-model组件通信提供的默认事件名和prop名约定(update: 前缀是用于指示这个事件是用来更新某个prop值的约定)。

父组件中,使用v-model通过v--model:modelValue绑定子组件定义的prop update:modelValue 事件。当父组件更新message时,会通过modelValue 将message传递给子组件。

子组件中,v-model会将modelValueupdate:modelValue 事件绑定在一起,当子组件的input事件触发时会emit一个update:modelValue 事件,从而更新父组件的message。

代码:

//Parent组件
{{ message }}
<hr />
<Child v-model:modelValue="message" />

import Child from '@/page/Child.vue'
import { ref } from 'vue'
let message = ref('parent的数据')

//Child组件
<input type="text" :value="modelValue" @input="updateValue" />
{{ modelValue }}

const props = defineProps(['modelValue'])
const emits = defineEmits(['update:modelValue'])
function updateValue(e) {
  emits('update:modelValue', e.target.value)
}


8.mitt

在vue 3中,已经没有提供配套的事件总线bus,需要使用第三方库mitt来完成vue 2中bus完成的事情。mitt是一个轻量级(仅200字节)的、功能强大的事件发射器/事件监听器库,非常适合在Vue项目或其他JS环境中使用来实现事件通信。使用Mitt可以任意组件之间进行通信

1)在项目中安装mitt

#npm
npm install mitt
#pnpm
pnpm install mitt

2)局部注册,创建mitt.js文件(实例化mitt,并对外暴露)

//引入emitt
import mitt from "mitt"
// 调用emitt得到emitter,emitter能:绑定事件,触发事件
const emitter = mitt()
export default emitter

全局注册的话,在man.js内引入mitt,并创建mitt实例,再将其挂载到Vue的全局属性上  

3)在需要使用的vue文件中导入emitter

import emitter from './mitt'

4)mitt核心语法

订阅事件(监听)

使用.on方法订阅(监听)一个或多个事件。当事件被触发时,指定的回调函数将被执行

// 监听名为'foo'的事件  
emitter.on('foo', (e) => {  
  console.log('foo event triggered', e);  
})
// 监听所有事件(使用'*'作为通配符)  
emitter.on('*', (type, e) => {  
  console.log(`${type} event triggered`, e);  
})

发布事件(触发)

使用.emit方法发布(触发)一个事件。可以传递任意数量的参数给监听该事件的回调函数。

// 触发名为'foo'的事件,并传递一个对象作为参数  
emitter.emit('foo', { a: 'b' });

取消订阅

使用.off方法取消订阅(移除)之前通过.on方法添加的事件监听器。

// 假设有一个名为onFoo的回调函数  
function onFoo(e) {  
  console.log('foo event handled', e)
}  
// 订阅事件  
emitter.on('foo', onFoo)
// 取消订阅  
emitter.off('foo', onFoo)
// 或者,如果你没有引用回调函数,可以取消所有监听'foo'事件的监听器  
emitter.off('foo')
// 取消所有事件的所有监听器  
emitter.all.clear()

命名空间

Mitt 支持命名空间,这有助于避免事件名称的冲突,并允许更精细地控制事件的监听和触发。

// 使用命名空间  
emitter.on('namespace/foo', (e) => {  
  console.log('namespaced foo event', e) 
})
  
emitter.emit('namespace/foo', { a: 'b' })

5)使用mitt的emit方法进行传值和on方法进行接收数据

// 组件A
bus.emit('函数名', 需要传的值)
//组件B
bus.on('函数名',(接收到的值)=>{
	逻辑操作
})

示例:

代码:

父组件Parent.vue

<ChildA></ChildA>
<ChildB></ChildB>

<script setup>
import ChildA from './ChildA.vue'
import ChildB from './ChildB.vue'
</script>

子组件ChildA.vue

<p>我是子组件A,我有:{{ computer }}</p>
<p>我会收到:{{ receive }}</p>
<button @click="triggerFun">传computer</button>

<script setup>
import { ref ,onUnmounted} from 'vue'
import emitter from './mitt'
//数据
let computer = ref('电脑')
let receive = ref('无')
//给emitter订阅监听send-toy事件。接收数据
emitter.on('send-toy',(item)=>{
    receive.value = item.value
})
// 传递数据
function triggerFun(){
    //触发emitter监听的事件send-computer
    emitter.emit('send-computer',computer)
}
// 组件卸载时取消订阅。考虑内存和污染
onUnmounted(()=>{
    emitter.off('send-toy')
})
</script>

子组件ChildB.vue

<p>我是子组件B,我有:{{ toy }}</p>
<p>我会收到:{{ receive }}</p>
<button @click="triggerFun">传toy</button>

<script setup>
import { ref } from 'vue'
import emitter from './mitt'
let toy = ref('玩具')
let receive = ref('无')
function triggerFun(){
    //触发emitter监听的事件send-toy
    emitter.emit('send-toy',toy)
}
//给emitter订阅监听send-computer事件。接收数据
emitter.on('send-computer',(item)=>{
    receive.value = item.value
})
</script>

9.pinia

推荐参考:Vue状态管理工具:Pinia

可创建一个专用于组件通信的store


若有错误或描述不当的地方,烦请评论或私信指正,万分感谢 😃

  • 12
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值