本文展示了一个基于Vue3的模态框组件实现,采用TypeScript编写。
组件通过Teleport实现DOM内容定位,使用props控制显示状态(isOpen),通过插槽(slot)支持内容自定义,并利用emit触发关闭事件。
特别介绍了<script setup>语法糖的改进实现,包括更简洁的props/emits定义(TypeScript类型安全)、自动暴露方法和scoped样式隔离。
组件具备条件渲染(v-if)、父子通信(props/emit)等特性,适合作为通用模态框解决方案。
代码分析
javascript
<template>
<teleport to="#modal">
<!-- teleport:将应该渲染在父组件中的内容渲染到指定位置 -->
<!-- teleport 是把已渲染的组件内容移动到目标节点,若组件没有被挂载(未在模板中使用),就不会有任何内容被 teleport 到目标节点。 -->
<div id="center" v-if="isOpen">
<!-- slot:插槽,可以插入自定义内容 -->
<h2><slot>this is a modal</slot></h2>
<div>
<button @click="closeModal()">close</button>
</div>
</div>
</teleport>
</template>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
props:{
isOpen:{
type: Boolean,
default: false
}
},
// emits:{// 向外触发自定义事件
// "close-modal":(payload:any)=>{
// //运行时校验
// return payload.type === "close";
// }
// },
//不需要验证
emits:{
"close-modal":null
},
// setup(props,context){
// //Modal.vue:28 [Vue warn]: Invalid event arguments: event validation failed for event "close-modal".
// //context.emit("close-modal",{type:"hello"});
// context.emit("close-modal",{type:"close"});
// }
//当前不需要验证
setup(props,context){
const closeModal=()=>{
context.emit("close-modal");
}
return {
closeModal
};
}
});
</script>
<style>
#center {
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
position: absolute;
top: 0;
left: 0;
border:2px solid rgb(42, 39, 39);
z-index: 1000;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: rgb(36, 35, 35);
}
</style>
这段代码是一个 Vue 3 模态框(Modal)组件,使用 TypeScript 编写。下面是对代码的详细解释:
模板部分 (<template>)
1. Teleport 组件
<teleport to="#modal">
-
作用 :将组件的内容渲染到 DOM 中的指定位置(
#modal),而不是默认的父组件位置 -
原理:组件逻辑上仍属于父组件,但 DOM 结构会移动到目标节点
-
注意:组件必须已经被挂载(在模板中使用)才会被 teleport
2. 模态框容器
<div id="center" v-if="isOpen">
v-if="isOpen":控制模态框显示/隐藏,由父组件传入的isOpenprop 控制
3. 插槽 (Slot)
<h2><slot>this is a modal</slot></h2>
-
提供默认内容 "this is a modal"
-
父组件可以传入自定义内容替换默认内容
4. 关闭按钮
<button @click="closeModal()">close</button>
- 点击触发
closeModal方法,发射自定义事件通知父组件
脚本部分 (<script>)
1. Props 定义
props:{
isOpen:{
type: Boolean,
default: false
}
}
-
接收父组件传入的
isOpen属性控制模态框显示状态 -
默认值为
false(不显示)
2. 自定义事件定义
// 有验证的版本(注释中)
emits:{
"close-modal":(payload:any)=>{
return payload.type === "close"; // 验证 payload
}
}
// 实际使用的无验证版本
emits:{
"close-modal":null // 不进行验证
}
-
声明组件会触发的自定义事件
-
有两种写法:带验证和不带验证
-
当前使用不带验证的版本
3. Setup 函数
setup(props,context){
const closeModal=()=>{
context.emit("close-modal"); // 触发事件
}
return {
closeModal // 暴露给模板使用
};
}
-
context.emit():触发自定义事件,通知父组件 -
返回
closeModal函数供模板绑定
样式部分 (<style>)
模态框样式
-
全屏覆盖 :
width: 100%; height: 100%; position: absolute; top: 0; left: 0; -
半透明背景 :
background-color: rgba(0, 0, 0, 0.5); -
层级最高 :
z-index: 1000;(确保在最上层) -
居中显示:使用 flex 布局实现水平和垂直居中
使用示例
父组件可能这样使用:
javascript
<template>
<div>
<button @click="showModal = true">打开模态框</button>
<Modal :isOpen="showModal" @close-modal="showModal = false">
自定义内容
</Modal>
<div id="modal"></div> <!-- Teleport 目标 -->
</div>
</template>
关键特性
-
Teleport:解决模态框的 z-index 和样式隔离问题
-
插槽:提供内容自定义能力
-
Props/Events:实现父子组件通信
-
条件渲染 :通过
v-if控制显示 -
样式隔离:模态框样式不会影响其他组件
script 部分改为 <script setup> 实现
以下是使用 <script setup> 语法重写的代码:
vue
javascript
<template>
<teleport to="#modal">
<div id="center" v-if="isOpen">
<h2><slot>this is a modal</slot></h2>
<div>
<button @click="closeModal">close</button>
</div>
</div>
</teleport>
</template>
<script setup lang="ts">
//import { defineProps, defineEmits } from "vue";
//defineProps 导入声明与局部声明冲突
//这个问题是因为在 <script setup> 中,defineProps 和 defineEmits 是编译时宏,不需要从 Vue 中显式导入。Vue 编译器会自动处理它们。
// 定义 props
const props = defineProps<{
isOpen: boolean;
}>();
// 定义 emits(两种写法)
// 写法1:简单声明(无参数验证)
const emit = defineEmits<{
(e: "close-modal"): void;
}>();
// 写法2:带参数验证
// const emit = defineEmits<{
// (e: "close-modal", payload: { type: string }): void;
// }>();
// 写法3:使用运行时声明(类似选项式API)
// defineEmits(["close-modal"]);
// const emit = defineEmits();
// 关闭模态框方法
const closeModal = () => {
// 触发自定义事件
emit("close-modal");
// 如果使用带验证的写法
// emit("close-modal", { type: "close" });
};
</script>
<style scoped>
/* 使用 scoped 让样式只作用于当前组件 */
#center {
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
position: absolute;
top: 0;
left: 0;
border: 2px solid rgb(42, 39, 39);
z-index: 1000;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: rgb(36, 35, 35);
}
</style>
主要改动和说明:
1. 使用 <script setup> 语法
-
更简洁的 Composition API 写法
-
不需要显式导出,顶层绑定会自动暴露给模板
2. Props 定义
const props = defineProps<{
isOpen: boolean;
}>();
-
使用 TypeScript 类型字面量,提供类型安全
-
等效于原来的选项式声明,但更简洁
3. Emits 定义(三种写法)
写法1:简单声明
const emit = defineEmits<{
(e: "close-modal"): void;
}>();
写法2:带参数验证
const emit = defineEmits<{
(e: "close-modal", payload: { type: string }): void;
}>();
写法3:运行时声明(向下兼容)
defineEmits(["close-modal"]);
4. 方法定义
const closeModal = () => {
emit("close-modal");
};
-
方法直接声明为顶层变量
-
无需在
setup函数中返回,自动暴露给模板
5. 样式作用域
<style scoped>
-
添加
scoped属性,使样式只作用于当前组件 -
避免样式污染全局
6. 模板中的使用
- 使用
closeModal或者closeModal()
在实际开发中,建议:
如果函数需要参数,使用带括号的写法
如果函数不需要参数,两种写法都可以,但带括号的写法更明确
两种写法的区别:
1.
@click="closeModal()"(带括号)
直接调用函数,可以传递参数
例如:
@click="closeModal(item)"2.
@click="closeModal"(不带括号)
Vue 会自动将事件对象作为第一个参数传递给函数
等价于:
@click="(...args) => closeModal(...args)"但前提是 :
closeModal函数本身接受参数
使用示例(父组件)
javascript
<template>
<div>
<button @click="showModal = true">打开模态框</button>
<!-- 使用组件 -->
<Modal :isOpen="showModal" @close-modal="handleClose">
自定义模态框内容
</Modal>
<!-- Teleport 目标 -->
<div id="modal"></div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import Modal from './Modal.vue';
const showModal = ref(false);
const handleClose = () => {
showModal.value = false;
};
</script>
<script setup> 的优势
-
更简洁的代码:减少样板代码
-
更好的类型推断:TypeScript 支持更完善
-
自动暴露:顶层绑定自动暴露给模板
-
更好的性能:编译时优化,运行时开销更小
-
更清晰的逻辑组织:相关逻辑可以分组在一起
这种写法是 Vue 3 推荐的 Composition API 写法,特别适合 TypeScript 项目。