揭秘Vue中组件通信 —— v-bind、emits、v-model、ref组件通信就是指组件之间的数据传递。由于组件的
前言:
组件通信就是指组件之间的数据传递。由于组件的数据是独立的,无法直接访问其他组件的数据,所以想要使用其他组件数据必须通过组件通信。
如图,组件①与组件②之间的通信:
可以看到组件①②各司其职,通过组件通信成功进行交互,完成了添加新值的效果。
那么接下来我们要讲的,正通过这两个组件来讲解一种父子通信,三种子父通信,共四种方法基础的通信方法,与实际应用它们需要注意的点。
初始状态:
先来看,这是一段没有将分这两个组件分开,写在一起时的初始代码,然后我们再来介绍将它们分开需要用到的组件通信方式:
<template>
// **********组件1**********
<div class="input-group">
<input type="text" v-model="value">
<button @click="add">添加</button>
</div>
// **********子组件2**********
<div class="child">
<ul>
<li v-for="item in list">{{item}}</li>
</ul>
</div>
</template>
<script setup>
import { ref } from 'vue'
const list = ref(['html', 'css', 'js'])
const value = ref('')
const add = () => {
list.value.push(value.value)
value.value = ''
}
</script>
<style lang="css" scoped>
</style>
组件通信方式:
父子组件通信:v-bind
现在我们把输入作为父组件,展示作为子组件来实现父子组件通信。

原理: 父组件将值通过v-bind
绑定传给子组件,子组件使用definePrope
接收。
在代码第八行的<child :msg="toChild"></child>
,我们通过使用v-bind
使msg
(自命名)绑定toChild
(点击添加按钮添加进来的值)值,传给子组件。
****************************************父组件****************************************
<template>
<div class="input-group">
<input type="text" v-model="value">
<button @click="add">添加</button>
</div>
<child :msg="toChild"></child> // 将值通过`v-bind`绑定传给子组件
</template>
<script setup>
import { ref } from 'vue'
import Child from '@/components/child.vue' // 以Child命名,导入子组件child.vue
const list = ref(['html', 'css', 'js'])
const value = ref('')
const toChild = ref('')
const add = () => {
toChild.value = value.value
}
</script>
<style lang="css" scoped>
</style>
顺带讲解一下子组件收到父组件的值,然后将其传入子组件数组的方法。这里先是第14行的defineProps
接收到父组件传的值,再通过watch
监听到msg
的值产生变更,于是push(newVal)
,将变更后的值推入数组。便达到了组件通信效果!
****************************************子组件****************************************
<template>
<div class="child">
<ul>
<li v-for="item in list">{{item}}</li>
</ul>
</div>
</template>
<script setup>
import { defineProps, ref, watch } from 'vue'
const list = ref(['html', 'css', 'js'])
const props = defineProps({ // 子组件使用`definePrope`接受
msg: ''
})
watch( // 监听defineProps穿进来的值是否发生变更,如是则推入数组
() => props.msg,
(newVal, oldVal)=>{
list.value.push(newVal)
}
)
</script>
<style lang="css" scoped>
</style>
子父组件通信①:emits
现在我们把输入作为子组件,展示作为父组件来实现三种子父组件通信。

以下是第一种子父通信实现,其原理是借助发布订阅机制,子组件负责发布事件并携带参数,父组件订阅事件通过事件参数获取子组件提供的值。这就是大名鼎鼎的发布订阅机制!
在第13行,我们通过const emits = defineEmits(['add1'])
创建了一个名为 add1 的事件,再通过emits('add1', value.value)
将这个事件发布。这里输入的值就作为事件参数,随事件一起发布。
****************************************子组件****************************************
<template>
<div class="input-group">
<input type="text" v-model="value">
<button @click="add">添加</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
const value = ref('')
const emits = defineEmits(['add1']) // 创建一个add事件
const add = () => {
// 将value给到父组件
emits('add1', value.value) // 发布事件
}
</script>
<style lang="css" scoped>
</style>
既然子组件发布了 add1 这个事件,父组件就在第4行通过@add1="handle"
命名一个 handle 来绑定这个事件,也被称为订阅add1事件。
正常流程下子组件接收到值,通过 add1 事件发布给父组件,于是父组件中 handle 函数调用,运行list.value.push(event)
将传过来的事件参数推入数组。便达到了组件通信效果!
<template>
****************************************父组件****************************************
<!-- 订阅add1事件 -->
<child @add1="handle"></child>
<div class="child">
<ul>
<li v-for="item in list">{{item}}</li>
</ul>
</div>
</template>
<script setup>
import Child from '@/components/child2.vue' // 以Child命名,导入子组件child.vue
import { ref } from 'vue'
const list = ref(['html', 'css', 'js'])
const handle = (event) => {
list.value.push(event)
}
</script>
<style lang="css" scoped>
</style>
如果把父组件的数组(const list = ref(['html', 'css', 'js']
))比作篮子,子组件中添加进来的新值比作苹果,这种方法就像子组件把苹果传递给父组件,父组件再把它放进篮子里。
子父组件通信②:v-model
原理:父组件借助v-model将数据绑定给子组件,子组件创建'update:xxx'事件,并将 接收到的数据修改后emits出来。
同样是实现子父通信,与①方法不同的是,这种方法做的是子组件把父组件的篮子拿过来,把苹果放进去,然后再把篮子还给父组件。
在父组件中,我们先通过v-model:list="list"
绑定 list 属性,并传递给父组件,父组件中同样用defineProps
接收。到这里算是初步完成了。
****************************************父组件****************************************
<template>
<child v-model:list="list"></child>
<div class="child">
<ul>
<li v-for="item in list">{{item}}</li>
</ul>
</div>
</template>
<script setup>
import Child from '@/components/child3.vue'
import { ref } from 'vue'
const list = ref(['html', 'css', 'js'])
</script>
<style lang="css" scoped>
</style>
为什么说是初步完成呢?因为实际应用中不建议直接操作父组件传过来的数据,因为这个数组属于父组件,如果子组件能修改父组件中的值,那多少是有些不合适的,面对一父多子的情况下也势必造成混乱。
因此我们使用const emits = defineEmits(['update:list'])
绑定数组 list 的更新事件,当24行的 push 将新值推入数组后,emits('update:list', arr)
又将这个事件抛出,这样 modle 就会接收到这个事件,做到实际上的值为父组件修改的效果!
****************************************子组件****************************************
<template>
<div class="input-group">
<input type="text" v-model="value">
<button @click="add">添加</button>
</div>
</template>
<script setup>
import { ref, defineProps } from 'vue'
const value = ref('')
const props = defineProps({ // 接收传过来的值
list: {
type: Array, // 当子组件中传过来的值属性不为Array时报错。
default: () => []
}
})
const emits = defineEmits(['update:list']) // 绑定更新事件
const add = () => {
// props.list.push(value.value) // 不建议直接操作父组件传过来的数据
const arr = props.list
arr.push(value.value)
emits('update:list', arr) // 抛出更新事件,使父组件接收
}
</script>
<style lang="css" scoped>
</style>
子父组件通信③:ref
原理:父组件通过 ref 获取子组件中 defineExpose() 暴露出来的数据
与上两种子父组件通信不同的是,现在我们要介绍的是一种数组(const list = ref(['html', 'css', 'js'])
)属于子组件下的通信(这样的好处是子组件可以直接把值添加进数组中)。
那么第一步我们要做的就是让父组件能读到子组件数组中的值,于是我们通过第4行与第19的ref="childRef"
与const childRef = ref(null)
创建childRef
变量并打到ref
身上,这样一来我们就能通过ref
访问到子组件通过defineExpose
提供出来的数据。
需要注意的是,这里父组件中的读取涉及到子父组件的加载顺序,简单来说就是父组件读取时子组件还没加载完的情况。于是我们使用第8行的原生方法childRef?.list
意思是childRef
中有值时再将 list 循环,实在是异常的方便。
****************************************父组件****************************************
<template>
<!-- 订阅add1事件 -->
<child ref="childRef"></child>
<div class="child">
<ul>
<li v-for="item in childRef?.list">{{item}}</li>
</ul>
</div>
</template>
<script setup>
import Child from '@/components/child4.vue'
import { onMounted, ref } from 'vue'
const childRef = ref(null) // 子组件动结构
</script>
<style lang="css" scoped>
</style>
在子组件通过defineExpose
将 list 暴露给父组件。
****************************************子组件****************************************
<template>
<div class="input-group">
<input type="text" v-model="value">
<button @click="add">添加</button>
</div>
</template>
<script setup>
import { ref, defineProps } from 'vue'
const value = ref('')
const list = ref(['html', 'css', 'js'])
const add = () => {
list.value.push(value.value)
}
defineExpose( {list} ) // 暴露出list给父组件
</script>
<style lang="css" scoped>
</style>
至此,大功告成!
最后
总结一下就是这里提供了一种父子组件通信,三种字符组件通信,它们的原理分别是:
-
父子组件通讯 --- 父组件将值 v-bind 绑定传给子组件,子组件使用 definePrope 接受。
-
子父组件通讯① --- 借助发布订阅机制,子组件负责发布事件并携带参数,父组件订阅事件通过事件 参数获取子组件提供的值。
-
子父组件通讯② --- 父组件借助 v-model 将数据绑定给子组件,子组件创建 'update:xxx' 事件,并将 接收到的数据修改后 emits 出来。
-
子父组件通讯③ --- 父组件通过 ref 获取子组件中 defineExpose() 暴露出来的数据。
以及实际应用上需要注意的各方面细节,感谢收看!
转载自:https://juejin.cn/post/7400671870872076322