想要了解父子组件之间的通信方式和使用我们首先要知道什么是组件通信?
组件通信就是指组件与组件之间的数据传递
工作中我们的代码量会非常多,为了后期便于维护和调整,我们可以把一个业务模块根据基础的功能模块拆成多个小组件,例如:list展示件、form 表单模块、弹窗等……此时我们就用到了父子组件通信。
(1)父子组件通信几种情况和关系
1.父组件的数据传递给子组件(父传子:单向传递 v-bind)
【父组件通过 props 将数据传递给子组件】
2.子组件的数据传递给父组件(子传父:单向传递 v-bind)
【子组件利用 $emit 通知父组件修改更新】
3.父子组件之间共享数据,即子组件可修改父组件数据 (父子双向绑定:双向绑定 v-model)
【子组件中通过defineEmits派发自定义事件触发父组件中的方法,父组件接收后,在父组件中修改】
(2)通信解决方案
将父组件的数据传递给子组件主要的思路是:
1.确定在子组件中是否需要修改父组件数据,即是否需要双向绑定
2.在父组件中将需要传递给子组件的数据进行绑定
3.在子组件中使用defineProps接收
注:
如果在子组件中不需要修改父组件的数据,直接使用v-bind进行绑定;
例如:
<LimitReportData :title="state.titleName"> </LimitReportData>
如果需要在子组件中修改父组件数据,则需要使用v-model进行双向绑定;
例如:
<LimitReportData v-model:title="state.titleName"> </LimitReportData>
(1)父传子:绑定值
我们在使用父组件传值给子组件时候,有时候会遇到在子组件中能接收到父组件的数据****但是渲染不出来的问题,因为父组件是异步获取的数据,在传递给子组件时有可能子组件已经渲染完了,此时子组件收到了值但是页面上并没有渲染出来。
解决方案有两种方式解决具体如下:
[1]、方式一、子组件不使用计算属性,但是需要watch监听
父组件:
在父组件中调用子组件,然后绑定需要传递的参数
(可以使用v-bind也可以使用v-model,v-model是双向绑定)
<LimitReportData
v-model:dialogVisible="state.dialogVisibleLimit" // 双向绑定的数据
:isAutoflag="state.autoReportValue" // 不需要双向绑定的数据
</LimitReportData>
子组件:
在子组件中使用defineProps接收
<el-dialog
// 因为这里是需要打开弹窗,所以需要在弹窗上也进行绑定
:model-value="dialogVisible"
@close="resetFormLimit()">
</el-dialog>
// 接收到的父组件传递过来的数据
const props = defineProps({
dialogVisible: Boolean,
isAutoflag:Boolean,
});
const emit = defineEmits(['update:dialogVisible']);
watch(() => props.dialogVisible,
() => {
state.flag = props.isAutoflag; // 监听到弹窗打开时的操作,例如: 初始数据的获取等等
},
{ deep: true, immediate: true, })
// 关闭弹窗时
function resetFormLimit() {
// 注意这里和第二种方式的区别
emit('update:dialogVisible', false);
}
【2】、方式二:子组件使用计算属性(推荐)
父组件方式不变,同上
子组件如下:
import { computed } from 'vue';
<el-dialog
v-model="dialogVisible"
@close="resetFormLimit()">
</el-dialog>
const props = defineProps({
dialogVisible: Boolean,
formData:formData的类型
});
const emit = defineEmits(['update:dialogVisible', 'update:formData']);
// 重点:使用计算属性监听数据变化
const dialogVisible = computed({
get() {
return props.dialogVisible;
},
set(val) {
emit('update:dialogVisible', val);
},
});
const formData= computed({
get() {
return props.formData;
},
set(val) {
emit('update:formData', val);
},
});
// 关闭弹窗时
function resetFormLimit() {
// 和第一种方式的区别,在需要更改数据回传给父组件时,可直接使用,不需要用emit()
dialogVisible.value = false;
}
使用计算机属性防止子组件数据渲染不出来的优点:
在子组件里面通过计算属性的好处是不用在需要更改数据的地方手动去 emit(‘update:’)来更改父组件的数据;
数据流向清晰
(2)父传子:绑定方法
父子组件绑定的方法给子组件,在子组件中我们通过defineEmits定义可以被触发的事件,从而是父组件能够监听到并且做出响应
父组件:
1.在父组件中将getReportTableData(searchForm.reportType)方法通过绑定到getReportTableData传给子组件
2.在子组件中接收getReportTableData这个方法
<LimitReportData
@getReportTableData="getReportTableData(searchForm.reportType)"
</LimitReportData>
子组件:
在子组件中使用defineEmits定义可以被触发的事件,从而可以使父组件能够监听到并作出响应。
<el-dialog
// 因为这里是需要打开弹窗,所以需要在弹窗上也进行绑定
:model-value="dialogVisible"
@close="resetFormLimit()">
</el-dialog>
const emit = defineEmits(['update:dialogVisible', 'getReportTableData']);
watch(() => props.dialogVisible,
() => {
state.flag = props.isAutoflag; // 监听到弹窗打开时的操作,例如: 初始数据的获取等等
},
{ deep: true, immediate: true, })
// 关闭弹窗时
function resetFormLimit() {
emit('getReportTableData', editForm.reportType);
}
在子组件调用父组件的方法时,使用emit(‘getReportTableData’, editForm.reportType);
editForm.reportType是方法中的参数;若不需要参数就直接emit(‘方法’)
当父组件想直接调用子组件的属性或者方法时,子组件使用defineExpose暴露自身的属性或者方法,父组件中使用ref调用子组件暴露的属性或方法。
(1) 子传父:在父组件中使用子组件的属性/方法
子组件
<el-dialog
:model-value="dialogVisible"
@close="resetFormLimit()">
</el-dialog>
const msgson = ref('son msg')
function clearForm() {
msgson.value = 'son msg'
console.log('clearForm是子组件里的方法, msgson 是父组件里的属性');
}
// 暴露clearForm方法和msgson属性给父组件
defineExpose({clearForm, msgson})
父组件
// 使用ref绑定子组件
<LimitReportData ref="sonDataRef"></LimitReportData>
const sonDataRef = ref(null)
onMounted(() => {
state.msg = sonDataRef?.value.msgson;// 使用子组件的msgson属性
sonDataRef?.value.clearForm(); // 使用子组件的clearForm()方法
})
(2)子组件控制修改父组件的数据
**vue的单向数据流:**数据的改变只能从父组件传递给子组件(父组件的更新会向下流动到子组件中),无法直接在子组件中修改父组件的数据。
1.思路:在子组件中通过defineEmits派发自定义事件触发父组件中的方法,父组件接收后,在父组件中修改。
2.例子:
需要在父组件中获取子组件传递的数据ID用于判断弹窗标题是新增还是修改操作。
1.在父组件中写获取id的方法
父组件:
// 获取新增、修改时传递的id
function handleEditId(val: string) {
state.editId = val;
}
2.在子组件中触发父组件中的handleEditId方法
子组件:
// 在子组件中派发handleEditId自定义事件,用于触发父组件中的handleEditId事件
const emit = defineEmits(['handleEditId']);
// 打开编辑弹窗时,向父组件传递当前数据ID
function openEditDialog(id: string) {
dialogVisible.value = true;
emit('handleEditId', id); // 修改父组件中editId
}
以往的vue2中我们可以使用事件总线方式eventBus
event bus 事件总线
1、创建一个都能访问的事件总线 (空Vue实例)
js import Vue from ‘vue’ const Bus = new Vue() export default Bus
2、A组件(接受方),监听Bus的 KaTeX parse error: Expected '}', got 'EOF' at end of input: …eated () { Bus.on(‘sendMsg’, (msg) => { this.msg = msg }) }
3、B组件(发送方),触发Bus的 e m i t 事件 v u e B u s . emit事件 vue Bus. emit事件vueBus.emit(‘sendMsg’, ‘这是一个消息’)
vue3中取消了 o n , on, on,off,$once, 不能使用事件总线的方式了
这里我们使用provide/inject或者使用mitt库
方式一:provide/inject
定义要发送的数据:provide
定义接收的数据:inject
// 父组件传递数据
provide: {
msg: "我是小仙女",
emotion: "超级开心"
}
//接收数据的组件
inject:['msg','emotion']
注意:provide中定义的数据不能在本组件中使用
一般provide最好写成一个函数返回一个对象,可以获取到data中的数据。要想provide中数据变成响应式的,用computed包裹。
import {computed} from 'vue'
// 向非父组件传递数据
provide() {
return {
msg: "我是小仙女",
emotion: "超级开心",
numsLength: computed(() => this.nums.length)
};
},
data() {
return {
nums: [1, 2, 3],
};
},
方式二:使用mitt库
安装mitt
npm install mitt
封装一下,文件utils/mitt.js
import mitt from "mitt";
const bus = mitt()
export default bus
层级关系:subChild1
App.vue 传递数据
<template>
<child1></child1>
<button @click="send">send</button>
</template>
<script>
import Child1 from "./Child1.vue";
import bus from "./utils/mitt";
export default {
components: {
Child1
},
data() {
return {
message:'你好吖'
}
},
methods: {
send() {
bus.emit("send", this.message);//发送数据
}
}
};
</script>
subChild1.vue 接收数据
<template>
<div>{{text}}</div>
</template>
<script>
import bus from './utils/mitt'
export default {
name:'SubChild1',
data() {
return {
text:''
}
},
created() {
bus.on('send',(data) =>{// 接收数据
this.text = data
})
},
}
</script>
发送数据:bus.emit('‘事件类型”,数据)
接收数据:bus.on(’'事件类型”,回调函数)
知道原理后,我们要回到实际开发应用中去:一般情况下
1、父组件->子组件:父组件发起 AJAX 请求,拿到数据后,再根据子组件的渲染需要传递不同的 props 给不同的子组件使用。
2、子组件->父组件:更新表单等数据量较多时非常好用。
3、涉及比较复杂或者跨级别组件,深度嵌套的组件,子组件想要获取父组件的部分内容,在这种情况下,如果我们仍然将props沿着组件链逐级传递下去,就会非常的麻烦。
对于这种情况下,我们可以使用 直接我们选择非父子直接通信