On this page
组合式API+Teleport实现Modal
结合Teleport的to=“body"属性和组合式API的状态管理,演示可复用的模态框组件实现。包括键盘事件监听、焦点管理及动画过渡的最佳实践。
考察点分析
核心能力维度
组合式API应用:考察逻辑复用能力与响应式状态管理
Teleport机制理解:评估对DOM挂载位置与样式作用域的掌握
可访问性实践:验证键盘交互与焦点管理的规范化实现
动画集成方案:测试过渡动画与组件生命周期的协调能力
技术评估点
- Teleport的DOM挂载策略及其对CSS层叠上下文的影响
- 组合式API中状态管理与组件通信的合理抽象
- 事件监听的绑定/解绑与内存泄漏防范
- 符合WAI-ARIA标准的焦点控制实现
- CSS Transition与组件挂载/卸载时序的协同
技术解析
关键知识点
Teleport > Composition API > Focus Trap > Keyboard Events > CSS Transition
原理剖析
Teleport机制:将模态框渲染至
<body>
末端,避免父组件position: relative
导致的定位失效。通过to
属性指定目标容器,保持全局样式稳定性响应式控制:使用
ref
管理开闭状态,通过defineEmits
暴露open/close
事件。采用v-model
语法糖实现双向绑定事件管理:组件挂载时注册
keydown
监听,处理ESC关闭。卸载时通过removeEventListener
防止内存泄漏。使用tabindex="-1"
与focus-trap
库实现焦点锁定动画集成:
<Transition>
组件配合enter-active-class
实现入场/离场动画。注意duration
属性与CSS动画时间的同步,防止DOM提前卸载
常见误区
- 未在
onUnmounted
清除事件监听导致内存泄漏 - 直接修改props违反单向数据流原则
- 使用
setTimeout
处理动画导致时序错乱 - 忽略
role="dialog"
等ARIA属性影响可访问性
问题解答
实现步骤如下:
- Teleport挂载:将模态框内容传送至body末端,避免CSS层级问题
- 组合式状态:使用
ref
控制显隐状态,通过defineEmits
触发状态变更 - 键盘监听:在
onMounted
注册ESC键监听,onUnmounted
移除 - 焦点管理:使用
autofocus
属性与focus-trap-vue
库实现焦点锁定 - 动画过渡:
<Transition>
包裹模态内容,配置enter-active
等动画类
解决方案
// Modal.vue
<template>
<Teleport :to="target">
<Transition name="modal" @after-leave="$emit('close')">
<div v-if="isOpen"
role="dialog"
class="modal-wrap"
@keydown.esc="handleClose">
<div class="modal-content">
<slot />
</div>
</div>
</Transition>
</Teleport>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
const props = defineProps({
modelValue: Boolean,
target: { type: String, default: 'body' }
})
const emit = defineEmits(['update:modelValue'])
const isOpen = ref(false)
// 控制显隐同步
watch(() => props.modelValue, val => isOpen.value = val)
watch(isOpen, val => emit('update:modelValue', val))
// ESC关闭处理
const handleClose = () => isOpen.value = false
// 键盘监听
const keyHandler = (e) => {
if (e.key === 'Escape') handleClose()
}
onMounted(() => window.addEventListener('keydown', keyHandler))
onUnmounted(() => window.removeEventListener('keydown', keyHandler))
</script>
<style>
.modal-enter-active, .modal-leave-active {
transition: opacity 0.3s ease;
}
.modal-enter-from, .modal-leave-to {
opacity: 0;
}
</style>
可扩展性建议
- 动态挂载点:通过
target
prop支持自定义挂载容器 - 动画增强:集成
animate.css
实现多样化过渡效果 - 全局状态:结合Pinia管理多模态框的叠加状态
- 性能优化:添加
lazy
修饰符实现按需加载模态内容
深度追问
追问1:如何实现点击外部关闭?
提示:使用@click.self
判断点击区域
追问2:如何处理多个模态框叠加?
提示:采用Z-index堆栈管理与暂停背景滚动
追问3:如何保证组件SSR兼容?
提示:在生命周期中判断window
对象存在性
Last updated 06 Mar 2025, 13:07 +0800 .