Vue中useSlots()获取插槽宽度仅首次有效问题排查与解决
问题描述
我通过如下代码获取插槽传入HTML元素的宽度:
<template> <div> <button @mouseover="state.show = true" @mouseleave="state.show = false"> hover me </button> <div v-if="state.show"> <slot name="test"></slot> </div> </div> </template> <script setup> import { reactive, watch, useSlots, nextTick } from 'vue' const slots = useSlots(); const state = reactive({ show: false }) watch(() => state.show, () => { if (state.show) { nextTick(() => { console.log(slots.test()[0].el.offsetWidth) }) } }) </script>
这段代码仅在首次hover按钮时输出正确宽度,后续hover均输出0。我还尝试在onUpdated钩子中使用getBoundingClientRect():
<script setup> import { reactive, watch, useSlots, nextTick, onUpdated } from 'vue' const slots = useSlots(); const state = reactive({ show: false }) onUpdated(() => { if (state.show && slots.test() && slots.test()[0] && slots.test()[0].el) { console.log(slots.test()[0].el.getBoundingClientRect()) } }) </script>
结果仍为仅首次hover显示正确宽度。请问出现该问题的原因是什么?如何实现每次hover按钮都能获取<slot name="test"></slot>的宽度?
问题原因
- 插槽DOM引用失效:当
v-if切换为false时,插槽所在的DOM容器会被销毁,但useSlots()返回的插槽对象里的el引用不会自动更新,后续hover时拿到的是已被移除的DOM节点,自然获取不到有效宽度。 useSlots()的特性限制:useSlots()返回的是当前渲染周期的插槽内容快照,首次渲染后如果插槽内容被销毁重建,旧的slots.test()[0].el无法指向新创建的DOM元素。
解决方案
放弃通过useSlots()获取DOM元素,改用**模板引用(ref)**绑定插槽容器,每次显示时通过ref获取最新的DOM节点:
<template> <div> <button @mouseover="state.show = true" @mouseleave="state.show = false"> hover me </button> <div v-if="state.show" ref="slotContainer"> <slot name="test"></slot> </div> </div> </template> <script setup> import { reactive, watch, ref, nextTick } from 'vue' const slotContainer = ref(null); const state = reactive({ show: false }) watch(() => state.show, async () => { if (state.show) { await nextTick(); // 等待DOM完成渲染更新 const slotElement = slotContainer.value?.firstElementChild; if (slotElement) { console.log('offsetWidth:', slotElement.offsetWidth); console.log('getBoundingClientRect宽度:', slotElement.getBoundingClientRect().width); } } }) </script>
说明
- 模板引用
slotContainer会在每次v-if创建容器时指向最新的DOM节点,确保获取到的是当前页面中存在的元素。 nextTick()用于等待Vue完成DOM更新流程,保证插槽元素已经渲染到页面,此时获取的宽度值是准确的。
内容的提问来源于stack exchange,提问作者BT101
相关产品推荐
相关产品推荐

