在日常的开发工作中,组件化是Vue.js的核心概念之一,而组件间的通信则是实现复杂应用逻辑的关键。今天,就让我们一起来探讨Vue中的组件通信,掌握这一技能,将有助于我们更好地构建高效、可维护的Vue应用。
我们通过一个简单的示例来了解组件间的通信是怎么实现的,我们先来实现一个如下效果;
实现上面的效果如果在一个组件内用代码非常简单实现,如下,只需要使用v-model绑定一个变量(inputValue)获取input框中的用户输入的数据,然后定义一个数组使用 v-for 在页面上循环展示整个数组,将数组以及变量均使用 ref 设为响应式,给添加按钮绑定一个点击事件add,往数组中添加 v-model 绑定的 inputValue,然后将其值设为空(使得下次输入时input框为空)。具体代码如下;
<template>
<div>
<div>
<input type="text" v-model="inputValue">
<button @click="add">添加</button>
<ul>
<li v-for="item in list">{{ item }}</li>
</ul>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const list = ref(['html', 'css', 'js'])
const inputValue = ref('')
const add = () => {
list.value.push(inputValue.value)
inputValue.value = ''
}
但是如果将上面的input框添加按钮与下面的数组遍历分别放在不同组件中应该怎么实现上面效果呢?
父子通信
如下;
parent.vue组件:
<template>
<div>
<div>
<input type="text" v-model="inputValue">
<button>添加</button>
// 将子组件的内容在这显示
<child></child>
</div>
</div>
</template>
<script setup>
import child from './child.vue'
const inputValue = ref('')
const add = () => {
}
</script>
child.vue组件:
<template>
<ul>
<li v-for="item in list">{{ item }}</li>
</ul>
</template>
<script setup>
import { ref } from 'vue'
const list = ref(['html', 'css', 'js'])
</script>
这也就是两个不同的文件,父组件为input框添加,然后引入子组件循环遍历li,这样样式是一样的,但是就无法实现添加显示功能,所以就需要将parent.vue组件中的input框中的数据传到子组件且push进数组进行打印,也就是父传子。那要怎么传值呢?既然子组件被父组件引入进来显示,那么就一定有某种联系。
我们可以在父组件中通过 v-bind 绑定属性将其数据传递给子组件,子组件通过 defineProps 接收付父组件绑定的属性,如下。
parent.vue组件:我们可以在<child :msg="data"></child>中绑定一个属性,然后点击事件将该属性的值变为输入框中的inputValue,如下。
<template>
<div>
<div>
<input type="text" v-model="inputValue">
<button @click="add">添加</button>
<child :msg="data"></child>
</div>
</div>
</template>
<script setup>
import child from './child.vue'
import { ref } from 'vue'
const inputValue = ref('')
const data = ref('')
const add = () => {
data.value = inputValue.value
inputValue.value = ''
}
child.vue组件:使用defineProps接收传入的值,然后push进数组。
<template>
<ul>
<li v-for="item in list">{{ item }}</li>
</ul>
</template>
<script setup>
import { ref } from 'vue'
const props = defineProps({
msg: {
type: String,
default: ''
}
})
const list = ref(['html', 'css', 'js'])
list.value.push(props.msg)
</script>
运行结果:
为什么会这样呢?看着上面代码仔细想想,在 child.vue 中,list.value.push(props.msg) 被放在 setup 函数中直接执行。这样,msg 会在子组件初始化时被添加到 list 中。而 msg 最开始是空字符串(''),那么空字符串就会被推入到 list,从而导致第四项为空,只要data值改变,msg就会跟着变化,但是只执行了这一次push操作,所以不能加入进去。
于是我们可以使用vue中的wtach监听 msg 改变,只要 msg 改变了,就执行一次push操作,这样就可以正确响应父组件中 data 的变化。如下;
<script setup>
import { ref } from 'vue'
const props = defineProps({
msg: {
type: String,
default: ''
}
})
const list = ref(['html', 'css', 'js'])
watch(
() => props.msg,
(newVal, oldVal) => {
list.value.push(newVal)
}
)
</script>
// 注意:在html里面可以直接使用 msg,但是在 js 中就必须 props.msg
子父通信
defineEmits/emit
子组件里面通过 defineEmits 定义一个事件,父组件通过@事件名="事件处理函数"监听子组件的事件(订阅行为),子组件通过 emit 方法发布事件,并传递参数(发布行为),具体代码如下;
parent.vue组件:
<template>
<child @add="adds"></child>
<ul>
<li v-for="item in list">{{ item }}</li>
</ul>
</template>
<script setup>
import child from './child.vue'
import { ref } from 'vue'
const list = ref(['html', 'css', 'js'])
const add = (e) => {
list.value.push(e)
}
</script>
<style lang="css" scoped></style>
在父组件的child标签绑定这个事件adds,@adds="add"来监听子组件触发的adds事件,并在adds方法中将传递过来的值添加到列表中。
child.vue组件:
<template>
<div>
<div>
<input type="text" v-model="inputValue">
<button @click="add">添加</button>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const inputValue = ref('')
const emit = defineEmits(['adds']) // 定义一个事件
const add = () => {
emit('adds', inputValue.value) // 发布事件
inputValue.value = '' // 清空输入框
}
</script>
子组件中add方法会被调用,发布名为adds的事件,并传递inputValue的值。
defineExpose/ref
父组件通过ref获取到子组件的dom结构,从而获取到子组件defineExpose()暴露出来的数据。子组件通过的defineExpose将自己的数据暴露出去。
parent.vue组件:
<template>
<child ref="childRef"></child>
<div>
<ul>
<li v-for="item in childRef.list">{{ item }}</li>
</ul>
</div>
</template>
<script setup>
import child from './child.vue'
import { ref } from 'vue'
const childRef = ref(null)
</script>
<style lang="css" scoped></style>
child.vue组件:
<template>
<div>
<input type="text" v-model="inputValue">
<button @click="add">添加</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
const list = ref(['html', 'css', 'js'])
const inputValue = ref('')
const add = () => {
list.value.push(inputValue.value)
inputValue.value = ''
}
defineExpose({
list: list
})
</script>
<style lang="css" scoped></style>
总结
- 父子通信:父组件通过 v-bind 绑定属性将自己的数据传递给子组件,子组件通过 defineProps 接收付父组件绑定的属性。
- 子父通信:子组件通过 defineEmits 定义一个事件,父组件通过@事件名="事件处理函数"监听子组件的事件(订阅行为),子组件通过 emit 方法发布事件,并传递参数(发布行为)。
子组件还可以通过 defineExpose 将自己的数据暴露出去,父组件通过 ref 来获取子组件的 dom 结构来获取暴露出来的数据。
通过上述两种通信方式,Vue组件可以灵活地进行数据交换,实现复杂的交互逻辑。父传子适合将数据从父组件传递到子组件,而子传父则适合将子组件的状态或事件结果传递回父组件。当然还有兄弟组件或不相关组件之间的通信,这就可以用到仓库的概念了,我们下次来聊,如果觉得本篇文章对你有所帮助的话可以点点赞哦🤗。