Vue.js修改含插槽的子组件数组时出现插槽内容显示异常的问题
Vue.js修改含插槽的子组件数组时出现插槽内容显示异常的问题
兄弟,我太懂你这种槽点了——明明组件标题都对得上,结果插槽内容却乱跳,肯定是Vue的虚拟DOM复用机制在搞鬼!
问题根源
你直接操作slots.default()返回的VNode数组时踩了个坑:这些VNode是和它们最初的组件实例绑定死的。Vue的diff算法会根据VNode的标识复用组件实例,当你把第一个VNode移到数组末尾时,Vue以为这还是原来的那个组件,所以不会更新它的插槽内容,导致插槽还停留在原来的上下文里,就出现了标题对、内容错的诡异情况。
解决方案:把插槽内容结构化,让Vue能正确追踪
我们不能直接拿VNode来挪位置,得先把每个子项的组件类型、props、插槽内容拆出来存成结构化对象,再给每个项加个唯一key,让Vue知道哪个是哪个,别乱复用。
1. 修改脚本部分,重新处理插槽内容
把原来直接赋值childComponents.value = slots.default()改成结构化处理:
<script setup> import { useSlots, ref, onMounted } from 'vue' const slots = useSlots() const childComponents = ref([]) const multiItemCarouselContainer = ref(null) let translateAmount = 0 onMounted(() => { // 把插槽元素转换成结构化对象,保存每个项的关键信息 childComponents.value = slots.default().map((child, idx) => ({ // 加个唯一ID,别用index当key! id: `carousel-item-${Date.now()}-${idx}`, type: child.type, props: child.props || {}, // 保存插槽内容 slotContent: child.children })) // 这里要在onMounted里计算,确保childComponents已经初始化 translateAmount = 100 / (childComponents.value.length + 1) }) function transitionNextFinished() { childComponents.value.shift() multiItemCarouselContainer.value.id = null multiItemCarouselContainer.value.style.transition = 'none' multiItemCarouselContainer.value.style.transform = 'none' multiItemCarouselContainer.value.removeEventListener('transitionend', transitionNextFinished) } function transitionNext() { const firstChild = childComponents.value[0] childComponents.value.push(firstChild) multiItemCarouselContainer.value.id = 'transitionNextElement' multiItemCarouselContainer.value.style.transition = 'transform .6s ease-in-out' multiItemCarouselContainer.value.style.transform = `translateX(-${translateAmount}%)` multiItemCarouselContainer.value.addEventListener('transitionend', transitionNextFinished) } </script>
2. 修改模板,用结构化对象渲染组件和插槽
把原来直接渲染VNode改成渲染我们的结构化对象,同时传递插槽内容:
<div class="container-fluid px-0 multi-item-carousel"> <div class="d-inline-flex multi-item-carousel-inner" ref="multiItemCarouselContainer"> <div class="me-3" v-for="item in childComponents" :key="item.id"> <component :is="item.type" v-bind="item.props"> <!-- 这里渲染保存的插槽内容 --> {{ item.slotContent }} </component> </div> </div> </div>
为什么这样能解决问题?
- 我们把每个子项的信息拆成了独立对象,Vue能通过唯一的
id来识别每个项,不会再随便复用组件实例。 - 插槽内容被我们单独保存并传递给组件,每次渲染都会用正确的内容,不会再被旧的组件实例上下文影响。
这样改完之后,你再点击按钮切换,标题和插槽内容就会一起正确移动啦!
备注:内容来源于stack exchange,提问作者Roland Deschain
相关产品推荐
相关产品推荐

