VUE3 父子组件通信
一、前言
在工作中有可能我们的代码量会非常多,为了后期便于维护和调整,我们可以把一个业务模块根据基础的功能模块拆成多个小组件,例如:list展示件、form 表单模块、弹窗等……此时我们就用到了父子组件通信。
注意:这里展示的是vue3的单文件组件<script setup>
中的使用方式
父子组件通信几种情况:
1.父组件的数据传递给子组件(父传子:单向传递 v-bind)
2.子组件的数据传递给父组件(子传父:单向传递 v-bind)
3.父子组件之间共享数据,即子组件可修改父组件数据 (父子双向绑定:双向绑定 v-model)
二、父传子
将父组件的数据传递给子组件主要的思路是:
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>
1.将父组件中state.dialogVisibleLimit
绑定到dialogVisible
上传递给子组件,在子组件中接收dialogVisible
。
2.将父组件中state.autoReportValue
绑定到isAutoflag
上传递给子组件,在子组件中接收isAutoflag
。
子组件:
在子组件中使用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);
}
1.使用defineProps
接收dialogVisible
和isAutoflag
;
2.如果需要在子组件中修改传递过来的值,则需要用defineEmits
定义一个可以被触发的事件,使父组件能够监听到并作出响应;
3.一般子组件里面emit('update:xxx')
主要针对 v-model 双向绑定的值使用,如果只是单纯的事件前面不用加 update: 直接 emit('xxx')
就行;
这里提一下,如果遇到在子组件中能接收到父组件的数据但是渲染不出来的问题,因为父组件是异步获取的数据,在传递给子组件时有可能子组件已经渲染完了,此时子组件收到了值但是页面上并没有渲染出来。
解决办法:利用watch进行监听,如上图的例子
相当于直接使用了el-dialog 的 v-model属性
问题:数据流动不太清晰,且如果需要修改数据时每次需要 emit('update:dialogVisible', false);
2.方式二:子组件使用计算属性(推荐)
父组件方式不变,同上
子组件如下:
<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:’)来更改父组件的数据;
数据流向清晰
(1) 父传子:绑定方法
父组件:
<LimitReportData
@getReportTableData="getReportTableData(searchForm.reportType)"
</LimitReportData>
1.在父组件中将getReportTableData(searchForm.reportType)
方法通过绑定到getReportTableData
传给子组件
2.在子组件中接收getReportTableData
这个方法
子组件:
在子组件中使用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
}