用喝一壶茶的时间学会vue的父子组件通讯
父子组件通讯
父子组件通讯是指在前端开发的组件化架构中,父组件与子组件之间互相传递数据和触发功能的一种机制。这种机制使得组件能够保持独立性的同时,也能协同工作,完成复杂的界面逻辑和数据交互。
通过一个用vue实现的demo为例:
在这个demo中:
- 需要使用
v-model
指令将文本框内的内容与变量进行双向绑定,用于获取文本框内的内容。 - 通过一个数组存储需要展示的内容,并且使用
v-for
遍历数组内的内容然后渲染。 - 通过
@click='add'
为按钮添加一个点击事件将文本框的输入内容添加到数组上。
//组件一
<div class="input-group">
<input type="text" v-model="item">
<button @click="add">添加</button>
</div>
//组件二(需要数据)
<div class="child">
<ul>
<li v-for="item in list">{{ item }}</li>
</ul>
</div>
如果我们将组件一放在父组件内,将组件二放在子组件,那我们要怎么将父组件中数据传递给子组件进行渲染呢?(组件二需要数据)
这个时候就需要进行父子组件的通讯。
父向子通讯
父组件向子组件传递信息非常方便,通过子组件的属性传递数据就好了。
组件一放在父组件,组件二放在子组件中。父组件将值v-bind
绑定传给子组件,子组件使用defineProps
接受。
父组件:在<Child :toChild="toChild"></Child>
中用v-bind绑定toChild
变量传递给子组件。
<template>
<div class="input-group">
<input type="text" v-model="item">
<button @click="add">添加</button>
</div>
//将数据传递给子组件
<Child :toChild="toChild"></Child>
</template>
<script setup>
import Child from '@/components/child1.vue'
import { ref } from "vue";
const item = ref('')
const toChild = ref('')
const add = () => {
toChild.value = item.value
item.value = ''
}
</script>
子组件:子组件用defineProps
接受父组件传递的数据并且通过设置一个watch
监听器,当父组件通过toChild
属性传递一个新的值,这个值就会被添加到子组件的列表中。
<template>
<div class="child">
<ul>
<li v-for="item in list">{{ item }}</li>
</ul>
</div>
</template>
<script setup>
import { defineProps } from 'vue'
import { reactive, watch } from "vue";
const list = reactive(['html', 'css', 'javascript'])
//接收父组件的数据
const props = defineProps({
toChild: ''
})
//监听数据的改变动态添加到数组里
watch(() => props.toChild, (newVal, oldVal) => {
list.push(newVal)
})
</script>
子向父通讯
子组件向父组件传递数据较为麻烦一点,常见的方法是通过自定义事件实现数据的传递。
将组件二放在父组件,将组件一放在子组件里。
方法一
借助发布订阅机制,子组件负责发布事件携带参数,父组件订阅该事件通过事件参数获取子组件提供的值。
子组件:通过defineEmits
定义并创建一个add
事件,再通过触发点击事件的处理函数用emits
发布add
事件,并且携带数据。
<template>
<div class="input-group">
<input type="text" v-model="item">
<button @click="add">添加</button>
</div>
</template>
<script setup>
import { ref } from "vue";
const item = ref('')
const emits = defineEmits(['add'])//创建一个add事件
const add = () => {
//将item变量给到父组件
emits('add', item.value)//发布add事件
item.value = ''
}
</script>
父组件:通过<child @add="handle"></child>
给子组件绑定自定义的add
事件并且以handle
函数作为处理函数。当add
事件发布时,就会触发执行handle
函数并且通过handle
函数的参数接收子组件传递的数据。
<template>
<!-- 订阅add事件,子组件什么时候发布add事件,父组件就会执行handle函数,从而实现数据的共享 -->
<child @add="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'
import { reactive } from "vue";
const list = reactive(['html', 'css', 'javascript'])
const handle = (value) => {
list.push(value)
}
</script>
这种方法中子组件只需要发布自定义事件并且携带数据,父组件只需要监听自定义事件并且接受数据。
方法二
父组件借助v-model
将数据绑定给子组件,子组件创建update:xxx
事件,并接收到该数据将修改后的数据emits(发布)出来让父组件接收修改后的数据。
父组件:在<child v-model:list="list"></child>
中,使用v-model:list
将父组件中的 list
数据(渲染数组)传递给子组件并且进行绑定,当子组件发布 update:list
事件后,父组件将接收到修改后的渲染数组并且进行重新渲染。
<template>
<div>
<child v-model:list="list"></child>
<div class="child">
<ul>
<li v-for="item in list">{{ item }}</li>
</ul>
</div>
</div>
</template>
<script setup>
import child from '@/components/child3.vue'
import {reactive } from "vue";
const list = reactive(['html', 'css', 'javascript'])
</script>
子组件:子组件通过defineProps
接收父组件发送的list
,并且自定义一个Update:list
事件。当点击按钮后触发add
函数,将文本框数据放入list
中,然后发布Update:list
事件并且携带着修改后的list
。随着Update:list
事件的发布,父组件就可以接收到修改后的list
。
<template>
<div>
<div class="input-group">
<input type="text" v-model="item">
<button @click="add">添加</button>
</div>
</div>
</template>
<script setup>
import { ref } from "vue";
const item = ref('')
const props = defineProps({
list: {
type: Array,
default: () => []
}
})
const emits = defineEmits(['Update:list'])
const add = () => {
const arr = props.list
arr.push(item.value)
emits('Update:list', arr)
item.value = ''
}
</script>
这种方法使父组件的操作变得简洁,但将子组件的操作变得复杂了。父组件只需要和子组件进行双向绑定数据就行了,子组件则需要接收数据再发布自定义的Update:xxx
事件将修改后的数据传递给父组件。
方法三
父组件通过ref
获取子组件中defineExprose()
暴露出来的数据。
子组件:使用 defineExpose
暴露数据,子组件通过defineExpose({ list })
将更新后的渲染数组暴露给父组件。
<template>
<div class="input-group">
<input type="text" v-model="item">
<button @click="add">添加</button>
</div>
</template>
<script setup>
import { ref, reactive } from "vue";
const item = ref('')
const list = reactive(['html', 'css', 'javascript'])
const add = () => {
list.push(item.value)
item.value = ''
}
defineExpose({ list })
</script>
父组件:获取子组件的数据,使用 ref
定义一个引用变量来引用子组件,并在适当的时机获取子组件暴露的渲染数组。将子组件暴露的渲染数组作为v-for
的循环对象进行渲染。
<template>
//获取子组件的引用
<child ref="childRef"></child>
<div class="child">
<ul>
//childRef?.list表示只有在在子组件已经挂载完成后才能访问子组件暴露的list数据
<li v-for="item in childRef?.list">{{ item }}</li>
</ul>
</div>
</template>
<script setup>
import child from '@/components/child4.vue'
import { ref, reactive, onMounted } from "vue";
//用ref定义一个引用变量
const childRef = ref(null);
</script>
这种方法十分简便,子组件只需要暴露数据给父组件,然后父组件引用子组件暴露的数据,但是需要注意生命周期,需要在组件挂载完成后父组件才能成功获取子组件的数据。
转载自:https://juejin.cn/post/7386540420020781082