尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

十五、Teleport与组件封装

十五、Teleport与组件封装 一、Teleport概述1. 基本概念简单来说Teleport 是 Vue 3 提供的一个“任意门”。在 Vue 中组件的 DOM 结构通常是跟着它的父组件走的。但有时候你写的代码逻辑上属于当前组件但在页面上却需要显示在别的地方比如直接放在body下面。Teleport 的作用就是把组件里的某段 HTML “瞬移”到指定的 DOM 节点去但它的“灵魂”数据、事件、逻辑依然留在原来的组件里。2. 使用场景想象一下你在一个嵌套很深的组件里写了一个弹窗Modal。如果不使用 Teleport你可能会遇到以下崩溃的场景被遮挡了父组件或者爷爷组件设置了z-index导致你的弹窗被别的元素压在下面。被裁切了父组件设置了overflow: hidden你的弹窗弹出一半就被“切”掉了。定位跑偏了父组件用了transform等属性导致你的position: fixed失效弹窗位置全乱了。典型场景模态框Modal、全局提示Toast、下拉菜单Dropdown、全屏 Loading 等。在企业级项目中你会发现像 Element Plus、Ant Design Vue 这样的顶级组件库其内部的 Dialog、Popover、Select 下拉框等底层全都是基于 Teleport Slots 这套架构实现的。二、参数1. to必填项这是Teleport最核心的参数用来指定你要把内容“传送”到哪个 DOM 节点。类型StringCSS 选择器或HTMLElement原生 DOM 节点常见用法传送到body下tobody传送到指定 id 的元素to#modal-container传送到指定 class 的元素to.overlay-root动态绑定你可以根据不同的屏幕尺寸或状态动态改变传送的目标位置。!-- 桌面端传送到 #desktop-target移动端传送到 #mobile-target -- Teleport :toisMobile ? #mobile-target : #desktop-target MyComponent / /Teleport2. disabled可选用来控制“传送门”是否关闭。当设置为true时内容不会发生传送而是老老实实地留在原组件的位置渲染。类型Boolean默认是false实际场景比如你在写一个下拉菜单在电脑端PC需要脱离父级悬浮显示但在手机端Mobile只需要在原地展开即可。Teleport tobody :disabledisMobile DropdownMenu / /Teleport3. defer可选Vue 3.5 新特性用来延迟解析目标元素。类型Boolean默认是false实际场景正常情况下Teleport挂载时目标元素必须已经存在于页面中。但如果你要传送到的目标容器也是由 Vue 渲染的且它在组件树中比你当前的组件还要靠后Vue 可能会找不到目标。加上defer后Vue 会推迟解析直到应用的其他部分都挂载完毕再去寻找目标。Teleport defer to#late-div !-- 传送的内容 -- /Teleport !-- 这个 div 虽然在下面但加了 defer 也能正确传送到这里 -- div idlate-div/div三、使用方法用法非常简单核心就是to属性它告诉你把内容传送到哪里。实际例子做一个全局弹窗template button clickvisible true打开弹窗/button !-- 开启传送门把里面的内容传送到 body 标签下 -- Teleport tobody div v-ifvisible classmodal-mask div classmodal-content p我是一个弹窗虽然写在组件里但我在 body 下哦/p button clickvisible false关闭/button /div /div /Teleport /template script setup import { ref } from vue; const visible ref(false); /script四、避坑指南坑一目标节点不存在如果你写Teleport to#my-container但页面里根本没有idmy-container的元素内容就会丢失控制台也会报警告。解决办法确保目标元素存在。最稳妥的做法是传送到body或者在index.html里提前写好一个空div idmy-container/div。坑二Scoped 样式失效如果你给弹窗写了style scoped发现样式没生效因为内容被传送到body下后脱离了当前组件scoped生成的唯一属性前缀就匹配不上了。解决办法去掉scoped变成全局样式或者使用:deep(.modal-mask)穿透样式推荐避免污染全局。坑三误以为它是路由跳转千万别写Teleport to/about它只操作 DOM 节点不管路由。坑四事件冒泡问题虽然 DOM 被移走了但事件依然是从原组件触发的。如果你在弹窗里点了按钮事件依然会顺着逻辑上的父组件往上冒泡。如果不需要记得在弹窗内部用click.stop阻止冒泡。注意事项在使用Teleport时一定要记住它只改变 DOM 的挂载位置不改变组件的逻辑上下文这意味着被传送到body下的组件它的 props 传值、事件触发$emit、依赖注入provide/inject等依然和原来的父组件保持联系。在 Vue Devtools 调试工具中它也会乖乖地嵌套在原来的父组件下面而不是跑到body下面去。五、封装企业级通用模态框 (Modal)设计思路使用 Teleport 将弹窗传送到body避免被父级样式遮挡。使用v-model语法糖实现双向绑定控制弹窗的显示与隐藏。使用具名插槽如header、default、footer让父组件可以随意定制弹窗内部的内容。1. 子组件BaseModal.vuetemplate Teleport tobody !-- 配合 v-if 避免不必要的 DOM 渲染 -- div v-ifmodelValue classmodal-mask div classmodal-container !-- 头部插槽 -- div classmodal-header slot nameheader默认标题/slot button click$emit(update:modelValue, false)✖/button /div !-- 默认插槽主体内容 -- div classmodal-body slot/slot /div !-- 底部插槽 -- div classmodal-footer slot namefooter button click$emit(update:modelValue, false)关闭/button /slot /div /div /div /Teleport /template script setup // 声明支持 v-model defineProps({ modelValue: Boolean }); defineEmits([update:modelValue]); /script !-- 注意因为 Teleport 到了 body这里不能加 scoped或者需要用 :deep() -- style .modal-mask { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); display: flex; justify-content: center; align-items: center; z-index: 9999; } .modal-container { background: #fff; padding: 20px; border-radius: 8px; min-width: 400px; } .modal-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; font-weight: bold; } /style2. 父组件如何使用template button clickvisible true打开自定义弹窗/button BaseModal v-modelvisible !-- 定制头部 -- template #header span stylecolor: blue; 确认提交订单/span /template !-- 定制内容 -- p您确定要购买这件商品吗/p !-- 定制底部按钮 -- template #footer button clickvisible false取消/button button clickhandleConfirm stylebackground:blue; color:#fff; margin-left:10px;确认支付/button /template /BaseModal /template script setup import { ref } from vue; import BaseModal from ./BaseModal.vue; const visible ref(false); const handleConfirm () { console.log(支付成功); visible.value false; }; /script六、封装动态悬浮提示 (Tooltip)设计思路使用作用域插槽 (Scoped Slots) 将“触发元素”和“提示内容”分离。利用 Teleport 将提示框传送到body并通过动态绑定:style实现跟随鼠标或目标元素定位。1. 子组件BaseTooltip.vuetemplate div styledisplay: inline-block; !-- 触发区域通过作用域插槽把 show/hide 事件暴露给父组件 -- slot nametrigger :onMouseovershow :onMouseouthide/slot !-- 提示内容传送到 body -- Teleport tobody div v-ifisVisible classtooltip-box :style{ top: ${position.y}px, left: ${position.x}px } slot namecontent默认提示文字/slot /div /Teleport /div /template script setup import { ref } from vue; const isVisible ref(false); const position ref({ x: 0, y: 0 }); const show (event) { isVisible.value true; // 获取鼠标位置作为提示框的坐标 position.value { x: event.clientX 10, y: event.clientY 10 }; }; const hide () { isVisible.value false; }; /script style .tooltip-box { position: fixed; /* 必须是 fixed因为挂载到了 body */ background: #333; color: #fff; padding: 6px 10px; border-radius: 4px; font-size: 12px; z-index: 10000; pointer-events: none; /* 防止鼠标移到提示框上导致闪烁 */ } /style2. 父组件如何使用template BaseTooltip !-- 触发元素接收子组件传过来的鼠标事件 -- template #trigger{ onMouseover, onMouseout } button mouseoveronMouseover mouseoutonMouseout 鼠标放上来试试 /button /template !-- 提示内容可以写任何复杂的 HTML -- template #content strong高级提示/strong 这里可以放图标、列表甚至图片 /template /BaseTooltip /template script setup import BaseTooltip from ./BaseTooltip.vue; /script
返回列表