vue3父组件调用子组件el-dialog对话框

2023-12-13 10:57:08

vue3父组件调用子组件el-dialog对话框

在写项目的时候,经常要使用父子组件通讯,我已经写了很多篇博客来介绍父子组件通讯了,vue中的父子组件通讯方式有差不多10来种,最常用的就那么一两种,这里我介绍其中我认为最基础的两种。因为目标是在父组件中,通过点击按钮事件来打开子组件中的el-dialog对话框,所以比传统的父传子要复杂一点。

先捋一下逻辑:

  1. 父组件中,点击按钮,给子组件传递一个打开对话框的信息

image-20231212180558769

  1. 子组件接收数据,控制对话框显示

image-20231212180714023

一、基础的props/emits父子传值

先看看父组件中的写法

<template>
    <BreadCrumb ref="breadCrumb" :item="item"></BreadCrumb>
    <div class="table-wrapped">
        <div class="table-top">
            <div class="table-header">
                <div class="search-wrapped">
                    <el-input v-model="input1" class="w-50 m-2" placeholder="输入账号搜索" :prefix-icon="Search" />
                </div>
                <div class="button-wrapped">
                    <el-button type="primary" @click="create">添加产品管理员</el-button>
                </div>
            </div>
        </div>
    </div>
    <CreateAdmin :isShow="isShowDialog"></CreateAdmin>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import { Search } from '@element-plus/icons-vue'
import BreadCrumb from '@/components/BreadCrumb.vue';
import CreateAdmin from '../components/CreateAdmin.vue'

const isShowDialog = ref(false)
const item = ref({
    first: '用户管理',
    second: '产品管理员'
})
const input1 = ref('')
const tableData = ref([])
const create = () => {
    isShowDialog.value = true
}
</script>

<style lang="scss" scoped>

</style>

父组件中的逻辑是,点击按钮后,将isShowDialog的值改为true,同时把isShowDialog赋值给isShow传递给子组件

再看看子组件中的写法:

<template>
    <el-dialog v-model="dialogVisible" title="创建管理员" width="30%" center>

    </el-dialog>
</template>
<script setup>
import { onMounted, ref, watch } from 'vue'

const dialogVisible = ref(false)
const props = defineProps({
    isShow: {
        type: Boolean,
        default: false
    }
})

watch(() => props.isShow, (val) => {
    console.log(val)
    dialogVisible.value = val
}, { immediate: true })

</script>
<style scoped></style>
  

子组件定义props接收父组件传递过来的值,然后赋值给dialogVisible,el-dialog根据这个dialogVisible来控制对话框是否展示

而且还使用watch深度监视了isShow的变化,当值变化时,马上把最新的值传递给dialogVisible

理论上,这样是不是可以很好的控制对话框的打开与关闭呢?实际上,根本就没法控制,也不能这么说,就第一次可以控制,然后关闭对话框后,就没法正常打开对话框了,什么原因呢,因为在关闭对话框的时候,没用通知父组件来修改对应的值,所以只能正常执行一次。来梳理一下上面代码的逻辑:

  1. 父组件传递给子组件isShow的初始值为false
  2. 父组件中点击按钮后,修改了isShow的值为true
  3. 子组件中的watch监听到isShow的变化,将最新的值(true)赋值给dialogVisible,从而控制对话框的打开
  4. 子组件关闭对话框,dialogVisible的值变为false
  5. 当再次点击父组件按钮想要打开对话框的时候,由于isShow的值仍然为true,所以子组件根本就没有监听到父组件传递过来的isShow的变化,所以对话框就再也打不开了

因此,上面的逻辑中缺少一项,就是在关闭对话框的时候,子组件给父组件传递信号,将isShow的值改为false

因此正确的写法应该是这样的:

父组件:

<template>
    <BreadCrumb ref="breadCrumb" :item="item"></BreadCrumb>
    <div class="table-wrapped">
        <div class="table-top">
            <div class="table-header">
                <div class="search-wrapped">
                    <el-input v-model="input1" class="w-50 m-2" placeholder="输入账号搜索" :prefix-icon="Search" />
                </div>
                <div class="button-wrapped">
                    <el-button type="primary" @click="create">添加产品管理员</el-button>
                </div>
            </div>
            
    </div>
    <CreateAdmin :isShow="isShowDialog" @close="closeDialog"></CreateAdmin>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import { Search } from '@element-plus/icons-vue'
import BreadCrumb from '@/components/BreadCrumb.vue';
import CreateAdmin from '../components/CreateAdmin.vue'

const isShowDialog = ref(false)
const item = ref({
    first: '用户管理',
    second: '产品管理员'
})
const input1 = ref('')
const tableData = ref([])
const create = () => {
    isShowDialog.value = true
}
const closeDialog = (val) => {
    isShowDialog.value = val
}
</script>

<style lang="scss" scoped>

</style>

子组件:

<template>
    <el-dialog v-model="dialogVisible" title="创建管理员" width="30%" center @close="closeDialog">

    </el-dialog>
</template>
<script setup>
import { onMounted, ref, watch } from 'vue'
import { creatAdminAPI } from "@/apis/userinfo";

const dialogVisible = ref(false)
const props = defineProps({
    isShow: {
        type: Boolean,
        default: false
    }
})

const emits = defineEmits(['close'])
const closeDialog = () => {
    emits('close', false)
}

watch(() => props.isShow, (val) => {
    console.log(val)
    dialogVisible.value = val
}, { immediate: true })

</script>
<style scoped></style>
  

先看子组件,在子组件的el-dialog对话框中,除了v-model="dialogVisible"这个关键属性外,还定义了它的关闭事件,当关闭对话框的时候,子组件向父组件发送一个close指令,并传递一个false值给父组件

再看父组件,父组件接收close事件,并在closeDialog中将isShowDialog的值设置为子组件传递过来的值,也就是false

这样,就可以来回控制了

也就是说,控制对话框的打开关闭,实际上要实现父传子和子传父两个过程,复杂就复杂在这里

这也是我不推荐用这种方式的原因

二、使用第三方工具mitt.js

我现在凡是涉及到父子组件传值,第一个想到的就是mitt,在vue2中,它是内置的api,叫事件总线,不知道为啥vue3移除了。mitt可以实现任意组件中的通讯,父子、兄弟、祖孙等等。

我之前写过一篇博客,专门用来解释vue3中使用第三方插件mitt实现任意组件通讯

这篇博客可能应用场景太复杂了,用在今天这个例子我觉得正合适。

使用步骤如下:

  1. 安装第三方包,并封装后导出,这个步骤我在之前的博客中已经写了详细的步骤
  2. 父组件中点击按钮后,发出打开对话框指令,代码如下,从代码中可以看出,不需要经过繁琐的传值过程,直接发送emit的openDialog指令就行了
<template>
    <BreadCrumb ref="breadCrumb" :item="item"></BreadCrumb>
    <div class="table-wrapped">
        <div class="table-top">
            <div class="table-header">
                <div class="search-wrapped">
                    <el-input v-model="input1" class="w-50 m-2" placeholder="输入账号搜索" :prefix-icon="Search" />
                </div>
                <div class="button-wrapped">
                    <el-button type="primary" @click="create">添加产品管理员</el-button>
                </div>
            </div>
    </div>
    <CreateAdmin></CreateAdmin>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import { Search } from '@element-plus/icons-vue'
import BreadCrumb from '@/components/BreadCrumb.vue';
import CreateAdmin from '../components/CreateAdmin.vue'
import mitt from '@/utils/mitt'
const emitter = mitt

const item = ref({
    first: '用户管理',
    second: '产品管理员'
})
const input1 = ref('')
const tableData = ref([])
const create = () => {
    emitter.emit('openDialog')
}

</script>

<style lang="scss" scoped>

</style>
  1. 子组件接收指令,代码如下,同样,子组件也不用再给父组件传递自定义事件,接收到openDialog指令后,修改dialogVisible的值为true就行了
<template>
    <el-dialog v-model="dialogVisible" title="创建管理员" width="30%" center>

    </el-dialog>
</template>
<script setup>
import { onMounted, ref, watch } from 'vue'
import { creatAdminAPI } from "@/apis/userinfo";
import mitt from '@/utils/mitt'
const emitter = mitt

const dialogVisible = ref(false)

emitter.on('openDialog', () => {
    dialogVisible.value = true
})
</script>
<style scoped></style>
  

两者一对比,就发现使用mitt真的会清爽多了,这也是我为啥钟情mitt的原因

如果要存储数据,可以使用mitt结合pinia或者vuex

三、使用ref控制

还有一种更简单的控制方式,不需要mitt,使用ref来实现

父组件:

<template>
    <BreadCrumb ref="breadCrumb" :item="item"></BreadCrumb>
    <div class="table-wrapped">
        <div class="table-top">
            <div class="table-header">
                <div class="search-wrapped">
                    <el-input v-model="input1" class="w-50 m-2" placeholder="输入账号搜索" :prefix-icon="Search" />
                </div>
                <div class="button-wrapped">
                    <el-button type="primary" @click="create">添加产品管理员</el-button>
                </div>
            </div>
    </div>
    <CreateAdmin ref=createRef></CreateAdmin>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import { Search } from '@element-plus/icons-vue'
import BreadCrumb from '@/components/BreadCrumb.vue';
import CreateAdmin from '../components/CreateAdmin.vue'

const item = ref({
    first: '用户管理',
    second: '产品管理员'
})
const input1 = ref('')
const tableData = ref([])
const createRef = ref()
const create = () => {
    createRef.value.open()
}

</script>

<style lang="scss" scoped>

</style>

子组件:

<template>
    <el-dialog v-model="dialogVisible" title="创建管理员" width="30%" center>

    </el-dialog>
</template>
<script setup>
import { onMounted, ref, watch } from 'vue'
import { creatAdminAPI } from "@/apis/userinfo";

const dialogVisible = ref(false)
const open = () => {
    dialogVisible.value = true
}
defineExpose({
    open
})
</script>
<style scoped></style>

可以看到,在父组件中,使用ref获取了子组件的dom,点击按钮时,调用子组件中的open方法

在子组件中,定义一个open方法,实际上就是将dialogVisible的值改为true,随后将这个open方法暴露出去

文章来源:https://blog.csdn.net/u012848304/article/details/134962810
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。