如何在Nuxtjs3中正确使用Suspense实现轮播骨架屏
Nuxt3中Suspense结合vue3-carousel失效问题解决
问题根源
你当前的实现有两个核心问题:
- 组件本身通过
<script setup>的顶层await已经成为异步组件,Nuxt会自动处理其加载状态,但你在模板内嵌套的<Suspense>与之产生冲突 vue3-carousel的<Carousel>组件要求直接子元素是<Slide>,而你用<Suspense>包裹了所有<Slide>,导致Carousel无法正确识别并渲染Slide元素,即便数据加载完成也会显示空白
修复方案
方案1:移除模板内Suspense,通过v-if控制骨架屏
直接利用组件的异步状态,通过preferredTours的存在与否来切换骨架屏和真实内容:
<template> <div class="ttc-py-1 sm:ttc-py-5"> <div class="ttc-container ttc-mx-auto"> <SectionHeader :heading="`Activities near you`" /> <client-only> <Carousel :items-to-show="3.5" :wrap-around="true" :breakpoints="breakpoints" > <!-- 真实内容 --> <Slide v-for="(item, index) in preferredTours" :key="item.uniqueId" <!-- 建议用唯一ID而非index作为key --> > <CardsActivityCard :city="item.city" :country="item.country" :image="`${item.image}`" :packagename="item.package_name" :price="item.price" :uniqueid="item.uniqueId" :slug="item.slug" /> </Slide> <!-- 骨架屏:仅在数据未加载时显示 --> <Slide v-for="i in 5" :key="`skeleton-${i}`" v-if="!preferredTours"> <div>loading ...... or Skeltons</div> </Slide> </Carousel> </client-only> </div> </div> </template> <script setup> import { ref } from "vue"; const { apiUrl } = useRuntimeConfig(); import { Carousel, Slide, Pagination, Navigation } from "vue3-carousel"; import "vue3-carousel/dist/carousel.css"; const [{ data: FavDests }, { data: preferredTours }] = await Promise.all([ useFetch(`${apiUrl}/dests/getpopulardests?country=${userCountry}`, { key: "cQsyfYspDP2zM1xMe0YY9Gfv4CgQByAd" } ), useFetch( `${apiUrl}/tours/getpopulartours?country=${userCountry}&usercurrency=${userCurrency}`, { key: "Z1rvmHPxAOLvt68WZZq2BUNsHQ8I213Y" } ), ]); </script>
方案2:拆分异步子组件,用Suspense包裹
把轮播的内容部分拆成独立的异步组件,让子组件处理数据请求,父组件用<Suspense>控制加载状态:
子组件:TourCarouselItems.vue
<template> <Slide v-for="(item, index) in preferredTours" :key="item.uniqueId" > <CardsActivityCard :city="item.city" :country="item.country" :image="`${item.image}`" :packagename="item.package_name" :price="item.price" :uniqueid="item.uniqueId" :slug="item.slug" /> </Slide> </template> <script setup> const { apiUrl } = useRuntimeConfig(); // 接收父组件传递的参数 const props = defineProps(['userCountry', 'userCurrency']); const { data: preferredTours } = await useFetch( `${apiUrl}/tours/getpopulartours?country=${props.userCountry}&usercurrency=${props.userCurrency}`, { key: "Z1rvmHPxAOLvt68WZZq2BUNsHQ8I213Y" } ); </script>
父组件修改:
<template> <div class="ttc-py-1 sm:ttc-py-5"> <div class="ttc-container ttc-mx-auto"> <SectionHeader :heading="`Activities near you`" /> <client-only> <Carousel :items-to-show="3.5" :wrap-around="true" :breakpoints="breakpoints" > <Suspense> <TourCarouselItems :userCountry="userCountry" :userCurrency="userCurrency" /> <template #fallback> <Slide v-for="i in 5" :key="`skeleton-${i}`"> <div>loading ...... or Skeltons</div> </Slide> </template> </Suspense> </Carousel> </client-only> </div> </div> </template> <script setup> import { ref } from "vue"; const { apiUrl } = useRuntimeConfig(); import { Carousel, Slide, Pagination, Navigation } from "vue3-carousel"; import "vue3-carousel/dist/carousel.css"; // 导入子组件 import TourCarouselItems from "./TourCarouselItems.vue"; // 处理FavDests请求 const { data: FavDests } = await useFetch(`${apiUrl}/dests/getpopulardests?country=${userCountry}`, { key: "cQsyfYspDP2zM1xMe0YY9Gfv4CgQByAd" } ); </script>
额外优化点
- 不要用
index作为v-for的key,改用数据中唯一标识(比如item.uniqueId),避免列表更新时出现渲染异常 - 骨架屏可以替换为更接近真实卡片结构的骨架组件,提升用户体验
内容的提问来源于stack exchange,提问作者imthegrv
相关产品推荐
相关产品推荐

