前言
组件通讯是指在一个使用组件化架构的前端框架中,不同组件之间按照层次关系进行的数据交换。在Vue中,当一个组件(父组件)包含另一个组件(子组件)时,它们可以通过特定的机制相互传递数据和消息。
代码部分这里我们使用类似于todoList的页面作为示例:
我们在input框中输入后点击添加按钮,输入的内容会添加到list下方
父子组件通讯
父组件将值v-bind
绑定传给子组件,子组件使用defineProps
接收
打个比方:
父子组件通讯就好比家长和孩子之间的对话。
家长(父组件)可以给孩子(子组件)一些东西,比如零花钱(数据),这是通过props实现的。
而孩子如果需要什么,比如想买玩具,就会向家长喊一声(触发事件),家长听到后就可以决定是否满足孩子的需求(处理事件)。
这样一来,尽管家长和孩子(组件)各自独立,但他们之间还是能很好地交流和互动。
代码:
父组件:
js
<template>
<!-- 输入框和按钮 -->
<div class="input-group">
<!-- 输入框,其值与value响应式变量双向绑定 -->
<input type="text" v-model="value">
<!-- 绑定点击事件 -->
<button @click="add">添加</button>
</div>
<!-- 子组件,接收toChild的值作为msg属性 -->
<Child :msg="toChild"></Child>
</template>
<script setup>
// 导入子组件
import Child from '@/components/child.vue'
// 导入Vue的ref响应式引用
import { ref } from 'vue'
// 定义响应式变量,用于存储输入框的值
const value = ref('')
// 定义响应式变量,用于存储要传递给子组件的值
const toChild = ref('')
// 定义add函数,当按钮被点击时执行
const add = () => {
// 将输入框的值赋给toChild
toChild.value = value.value
}
</script>
<style lang="css" scoped></style>
子组件:
js
<template>
<!-- 显示列表 -->
<div class="child">
<ul>
<!-- 遍历list数组,显示每一个元素 -->
<li v-for="item in list">{{ item }}</li>
</ul>
</div>
</template>
<script setup>
// 导入Vue的ref和watch
import { ref, watch } from 'vue'
// 初始化list数组
const list = ref(['html', 'css', 'js'])
// 定义props,接收父组件传递的msg属性
const props = defineProps({
msg: ''
})
// 监听msg属性的变化
watch(
// 返回一个计算属性,表示msg的值
() => props.msg,
// 当msg发生变化时执行的回调函数
(newVal, oldVal) => {
// 将新的msg值添加到list数组中
list.value.push(newVal)
}
)
</script>
<!-- css没有东西 -->
<style lang="css" scoped></style>
当父组件的add
函数被调用时,它会将输入框的值赋给toChild
,这个值随后通过v-bind
传递给子组件的msg
属性。
由于子组件使用watch
监听了msg
的变化,因此每当msg
更新,新值都会自动追加到list
数组中,从而更新UI显示。
子父组件通讯
1. 发布订阅
1. 借助发布订阅机制,子组件负责发布事件并携带参数,父组件订阅该事件通过事件获取子组件提供的值
2. 子组件触发添加含有参数的分布事件,父组件添加订阅事件读取该值
父组件:
js
<template>
<!-- 显示列表 -->
<div class="child">
<ul>
<!-- 遍历list数组,显示每一个元素 -->
<li v-for="item in list">{{ item }}</li>
</ul>
</div>
</template>
<script setup>
// 导入子组件Child,这里我的子组件文件名为child2.vue
import Child from "@/components/child2.vue";
// 导入Vue的ref响应式引用
import { ref } from 'vue';
// 定义响应式变量list,用于存储数据项
const list = ref(['html', 'css', 'js'])
// 定义handle方法,用于处理子组件发送的数据
const handle = (event) => {
// 打印接收到的数据
console.log(event);
// 将接收到的数据添加到list数组中
list.value.push(event)
}
</script>
<style lang="css" scoped></style>
tianjia
:订阅该事件:<Child @tianjia="handle"></Child>
。 也就是说每当子组件触发名为tianjia
的事件时,父组件的handle
方法将被执行。handle
:处理从子组件传来的数据,并将其推送到list
数组中。
子组件:
js
<template>
<div class="input-group">
<!-- 输入框,其值与value响应式变量双向绑定 -->
<input type="text" v-model="value">
<!-- 按钮,点击时触发add函数 -->
<button @click="add">添加</button>
</div>
</template>
<script setup>
import { ref } from 'vue';
const value = ref('')
// 创建可触发的事件对象,声明'tianjia'事件
const emits = defineEmits(['tianjia'])
// 定义add函数,当按钮被点击时执行
const add = () => {
// 触发'tianjia'事件,向父组件发送value的当前值
emits('tianjia', value.value)
}
</script>
<style lang="css" scoped></style>
- 子组件 :通过
defineEmits
声明可以触发的自定义事件tianjia
,并通过emits('tianjia', value.value)
将数据发送给父组件。 - 父组件 :在模板中通过
@tianjia="handle"
监听子组件的tianjia
事件,当事件触发时,执行handle
方法,将接收到的数据打印并添加到list
数组中,实现数据的更新和显示。
2. v-model数据绑定
父组件借助v-model将数据绑定给子组件,子组件创建'update:xxx'事件,并将接收到的数据修改后emits出来 父组件:
js
<template>
<!-- 使用v-model绑定list数据到子组件的list prop -->
<Child v-model:list="list"></Child>
<!-- 显示列表 -->
<div class="child">
<ul>
<!-- 遍历list数组,显示每一个元素 -->
<li v-for="item in list">{{ item }}</li>
</ul>
</div>
</template>
<script setup>
// 导入子组件Child
import Child from "@/components/child3.vue";
// 导入Vue的ref响应式引用
import { ref } from 'vue';
// 定义响应式变量list,用于存储数据项
const list = ref(['html', 'css', 'js'])
</script>
<style lang="css" scoped></style>
子组件:
js
<template>
<!-- 输入框和按钮 -->
<div class="input-group">
<!-- 输入框,其值与value响应式变量双向绑定 -->
<input type="text" v-model="value">
<!-- 按钮,点击时触发add函数 -->
<button @click="add">添加</button>
</div>
</template>
<script setup>
// 导入Vue的ref响应式引用
import { ref } from 'vue';
// 定义响应式变量,用于存储输入框的值
const value = ref('')
// 定义props,接收父组件传递的list数据
const props = defineProps({
list: {
type: Array,
default: () => []
}
})
// 创建可触发的事件对象,声明'update:list'事件
const emits = defineEmits(['update:list'])
// 定义add函数,当按钮被点击时执行
const add = () => {
// 创建一个新数组,避免直接修改父组件传递的数组
const arr = [...props.list]
// 向数组中添加新值
arr.push(value.value)
// 触发'update:list'事件,向父组件发送更新后的数组
emits('update:list', arr)
}
</script>
<!-- 样式部分 -->
<style lang="css" scoped></style>
关键点解析:
-
父组件 :使用
v-model:list="list"
将list
数据绑定到子组件。v-model
背后实际上是解构为v-bind
和v-on
,分别绑定数据和监听特定的事件。 -
子组件:
- 通过
defineProps
定义list
属性,接收父组件的数据。 - 使用
defineEmits
声明update:list
事件,这是v-model
机制识别的特殊事件。 - 在
add
方法中,不直接修改props.list
,而是创建新数组并修改,然后通过emits('update:list', arr)
触发事件,将更新后的数组发送回父组件。
- 通过
3. defineExpose()暴露数据
父组件通过ref获取子组件中defineExpose()暴露出来的数据
父组件:
js
<template>
<!-- 创建子组件引用 -->
<Child ref="childRef"></Child>
<!-- 显示子组件中暴露的list数据 -->
<div class="child">
<ul>
<!-- 遍历childRef中的list数组,显示每一个元素 -->
<li v-for="item in childRef?.list">{{ item }}</li>
</ul>
</div>
</template>
<script setup>
// 导入子组件Child
import Child from "@/components/child4.vue";
// 导入Vue的ref响应式引用和onMounted生命周期钩子
import { ref, onMounted } from 'vue';
// 创建对子组件的引用
const childRef = ref(null)
// 使用onMounted确保子组件已经被挂载
onMounted(() => {
// 输出子组件实例,检查是否可以访问
console.log(childRef.value);
})
</script>
<style lang="css" scoped></style>
子组件:
js
<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 list = ref(['html', 'css', 'js'])
// 定义add函数,当按钮被点击时执行
const add = () => {
// 向list数组中添加新值
list.value.push(value.value)
}
// 使用defineExpose明确指定哪些数据或方法对外公开
defineExpose({ list })
</script>
<style lang="css" scoped></style>
关键点解析:
- 父组件 :通过
ref
创建对子组件的引用childRef
,并在onMounted
钩子中安全地访问子组件实例。在模板中使用childRef?.list
来访问子组件中暴露的list
数据。 - 子组件 :使用
defineExpose
暴露list
数据,使得父组件或其他外部组件能够直接访问和操作list
。