当前位置: 首页 > news >正文

Vue 3 Suspense 的用法及使用

Vue3 Suspense 的用法及使用

文章目录

  • Vue3 Suspense 的用法及使用
    • 1. Suspense 概述
      • 1.1 什么是 Suspense
      • 1.2 核心概念
    • 2. 基本用法
      • 2.1 基本语法结构
      • 2.2 异步组件示例
    • 3. 异步 setup() 用法
      • 3.1 使用 async setup()
    • 4. 组合式 API 与 Suspense
      • 4.1 结合 composables 使用
    • 5. 错误处理
      • 5.1 使用 errorCaptured 钩子
    • 6. 嵌套 Suspense
      • 6.1 多层异步加载
    • 7. 实际应用场景
      • 7.1 路由级别的 Suspense
      • 7.2 数据预加载模式
    • 8. 高级模式和最佳实践
      • 8.1 可配置的 Suspense 包装器
      • 8.2 性能优化技巧
    • 9. 注意事项和限制
      • 9.1 使用限制
      • 9.2 最佳实践
    • 10. 总结
      • 主要优势:
      • 适用场景:

1. Suspense 概述

1.1 什么是 Suspense

Suspense 是 Vue 3 中用于处理异步组件加载的内置组件,它允许在等待异步组件时显示备用内容(fallback content)。

1.2 核心概念

  • 异步依赖:需要等待的异步操作,如异步组件、async setup() 等
  • 备用内容:在异步依赖未解析时显示的内容
  • 默认插槽:异步依赖解析后显示的实际内容

2. 基本用法

2.1 基本语法结构

<template><Suspense><!-- 默认插槽:显示主要内容 --><template #default><AsyncComponent /></template><!-- fallback 插槽:加载时显示备用内容 --><template #fallback><div>加载中...</div></template></Suspense>
</template>

2.2 异步组件示例

<template><Suspense><template #default><UserProfile /></template><template #fallback><div class="loading"><span>用户信息加载中...</span></div></template></Suspense>
</template><script setup lang="ts">
import { defineAsyncComponent } from 'vue'// 定义异步组件
const UserProfile = defineAsyncComponent(() =>import('./components/UserProfile.vue')
)
</script><style scoped>
.loading {display: flex;justify-content: center;align-items: center;height: 200px;font-size: 18px;color: #666;
}
</style>

3. 异步 setup() 用法

3.1 使用 async setup()

<!-- AsyncSetupComponent.vue -->
<template><div class="user-data"><h2>用户信息</h2><p>姓名: {{ user.name }}</p><p>邮箱: {{ user.email }}</p><p>角色: {{ user.role }}</p></div>
</template><script setup lang="ts">
import { ref } from 'vue'interface User {id: numbername: stringemail: stringrole: string
}const user = ref<User>({id: 0,name: '',email: '',role: ''
})// 模拟异步数据获取
const fetchUserData = (): Promise<User> => {return new Promise((resolve) => {setTimeout(() => {resolve({id: 1,name: '张三',email: 'zhangsan@example.com',role: '管理员'})}, 2000)})
}// 在 setup 中使用 await
const userData = await fetchUserData()
user.value = userData
</script>
<!-- ParentComponent.vue -->
<template><div class="container"><h1>用户管理</h1><Suspense @pending="onPending" @resolve="onResolve" @fallback="onFallback"><template #default><AsyncSetupComponent /></template><template #fallback><div class="skeleton"><div class="skeleton-line"></div><div class="skeleton-line"></div><div class="skeleton-line"></div></div></template></Suspense></div>
</template><script setup lang="ts">
import { ref } from 'vue'
import AsyncSetupComponent from './AsyncSetupComponent.vue'const loadingStatus = ref('')const onPending = () => {loadingStatus.value = 'pending'console.log('开始加载异步组件')
}const onResolve = () => {loadingStatus.value = 'resolved'console.log('异步组件加载完成')
}const onFallback = () => {loadingStatus.value = 'fallback'console.log('显示备用内容')
}
</script><style scoped>
.skeleton {padding: 20px;
}.skeleton-line {height: 20px;background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);background-size: 200% 100%;animation: loading 1.5s infinite;margin-bottom: 10px;border-radius: 4px;
}.skeleton-line:nth-child(2) {width: 80%;
}.skeleton-line:nth-child(3) {width: 60%;
}@keyframes loading {0% {background-position: 200% 0;}100% {background-position: -200% 0;}
}
</style>

4. 组合式 API 与 Suspense

4.1 结合 composables 使用

// composables/useAsyncData.ts
import { ref } from 'vue'export function useAsyncData<T>(asyncFn: () => Promise<T>) {const data = ref<T>()const loading = ref(true)const error = ref<Error | null>(null)const execute = async () => {try {loading.value = trueerror.value = nulldata.value = await asyncFn()} catch (err) {error.value = err as Error} finally {loading.value = false}}return {data,loading,error,execute}
}
<!-- DataComponent.vue -->
<template><div v-if="loading">加载中...</div><div v-else-if="error">错误: {{ error.message }}</div><div v-else><h3>产品列表</h3><ul><li v-for="product in data" :key="product.id">{{ product.name }} - ¥{{ product.price }}</li></ul></div>
</template><script setup lang="ts">
import { useAsyncData } from '@/composables/useAsyncData'interface Product {id: numbername: stringprice: number
}// 模拟 API 调用
const fetchProducts = (): Promise<Product[]> => {return new Promise((resolve) => {setTimeout(() => {resolve([{ id: 1, name: '产品A', price: 100 },{ id: 2, name: '产品B', price: 200 },{ id: 3, name: '产品C', price: 300 }])}, 1500)})
}const { data, loading, error } = useAsyncData(fetchProducts)
</script>
<!-- ParentWithSuspense.vue -->
<template><Suspense><template #default><DataComponent /></template><template #fallback><div class="loading-spinner"><div class="spinner"></div><p>数据加载中...</p></div></template></Suspense>
</template><script setup lang="ts">
import DataComponent from './DataComponent.vue'
</script><style scoped>
.loading-spinner {display: flex;flex-direction: column;align-items: center;justify-content: center;height: 200px;
}.spinner {width: 40px;height: 40px;border: 4px solid #f3f3f3;border-top: 4px solid #3498db;border-radius: 50%;animation: spin 1s linear infinite;margin-bottom: 10px;
}@keyframes spin {0% { transform: rotate(0deg); }100% { transform: rotate(360deg); }
}
</style>

5. 错误处理

5.1 使用 errorCaptured 钩子

<template><div><Suspense @error="onError"><template #default><AsyncComponent /></template><template #fallback><LoadingSpinner /></template></Suspense><div v-if="hasError" class="error-message"><h3>加载失败</h3><p>{{ errorMessage }}</p><button @click="retry">重试</button></div></div>
</template><script setup lang="ts">
import { ref } from 'vue'
import { defineAsyncComponent } from 'vue'const hasError = ref(false)
const errorMessage = ref('')// 模拟可能失败的异步组件
const AsyncComponent = defineAsyncComponent({loader: () => {return new Promise((resolve, reject) => {// 模拟 50% 的失败率if (Math.random() > 0.5) {setTimeout(() => {import('./AsyncComponent.vue').then(module => resolve(module))}, 1000)} else {setTimeout(() => {reject(new Error('组件加载失败'))}, 1000)}})}
})const onError = (error: any) => {hasError.value = trueerrorMessage.value = error.messageconsole.error('Suspense 错误:', error)
}const retry = () => {hasError.value = falseerrorMessage.value = ''// 重新加载逻辑location.reload()
}
</script><style scoped>
.error-message {background: #fee;border: 1px solid #fcc;border-radius: 4px;padding: 20px;margin: 10px 0;text-align: center;
}.error-message h3 {color: #c00;margin-bottom: 10px;
}
</style>

6. 嵌套 Suspense

6.1 多层异步加载

<template><div class="dashboard"><Suspense><template #default><div class="dashboard-content"><!-- 用户信息区域 --><Suspense><template #default><UserProfile /></template><template #fallback><ProfileSkeleton /></template></Suspense><!-- 数据统计区域 --><Suspense><template #default><StatisticsPanel /></template><template #fallback><StatisticsSkeleton /></template></Suspense><!-- 最近活动区域 --><Suspense><template #default><RecentActivity /></template><template #fallback><ActivitySkeleton /></template></Suspense></div></template><template #fallback><div class="global-loading"><h2>仪表板加载中...</h2><div class="progress-bar"><div class="progress"></div></div></div></template></Suspense></div>
</template><script setup lang="ts">
import { defineAsyncComponent } from 'vue'// 定义多个异步组件
const UserProfile = defineAsyncComponent(() =>import('./components/UserProfile.vue')
)const StatisticsPanel = defineAsyncComponent(() =>import('./components/StatisticsPanel.vue')
)const RecentActivity = defineAsyncComponent(() =>import('./components/RecentActivity.vue')
)const ProfileSkeleton = defineAsyncComponent(() =>import('./components/skeletons/ProfileSkeleton.vue')
)const StatisticsSkeleton = defineAsyncComponent(() =>import('./components/skeletons/StatisticsSkeleton.vue')
)const ActivitySkeleton = defineAsyncComponent(() =>import('./components/skeletons/ActivitySkeleton.vue')
)
</script><style scoped>
.dashboard-content {display: grid;grid-template-columns: 1fr 1fr;grid-gap: 20px;padding: 20px;
}.global-loading {display: flex;flex-direction: column;align-items: center;justify-content: center;height: 400px;
}.progress-bar {width: 300px;height: 4px;background: #f0f0f0;border-radius: 2px;margin-top: 20px;overflow: hidden;
}.progress {height: 100%;background: #3498db;animation: progress 2s ease-in-out infinite;
}@keyframes progress {0% { transform: translateX(-100%); }50% { transform: translateX(0%); }100% { transform: translateX(100%); }
}
</style>

7. 实际应用场景

7.1 路由级别的 Suspense

// router/index.ts
import { createRouter, createWebHistory } from 'vue-router'const router = createRouter({history: createWebHistory(),routes: [{path: '/',name: 'Home',component: () => import('@/views/Home.vue')},{path: '/about',name: 'About',component: () => import('@/views/About.vue')},{path: '/products',name: 'Products',component: () => import('@/views/Products.vue')}]
})export default router
<!-- App.vue -->
<template><div id="app"><nav><router-link to="/">首页</router-link><router-link to="/about">关于</router-link><router-link to="/products">产品</router-link></nav><main><RouterView v-slot="{ Component }"><Suspense><template #default><component :is="Component" /></template><template #fallback><GlobalLoading /></template></Suspense></RouterView></main></div>
</template><script setup lang="ts">
import { RouterView } from 'vue-router'
import GlobalLoading from './components/GlobalLoading.vue'
</script>

7.2 数据预加载模式

<template><Suspense><template #default><ProductDetail :product-id="productId" /></template><template #fallback><ProductDetailSkeleton /></template></Suspense>
</template><script setup lang="ts">
import { ref, onMounted } from 'vue'
import { defineAsyncComponent } from 'vue'interface Props {productId: string
}defineProps<Props>()// 预加载组件
const ProductDetail = defineAsyncComponent({loader: () => import('./ProductDetail.vue'),loadingComponent: defineAsyncComponent(() => import('./ProductDetailSkeleton.vue')),delay: 200,timeout: 3000
})
</script>

8. 高级模式和最佳实践

8.1 可配置的 Suspense 包装器

<!-- AsyncWrapper.vue -->
<template><Suspense:timeout="timeout"@pending="onPending"@resolve="onResolve"@fallback="onFallback"@error="onError"><template #default><slot name="default" /></template><template #fallback><slot name="fallback"><DefaultFallback :message="fallbackMessage" /></slot></template></Suspense>
</template><script setup lang="ts">
import { ref } from 'vue'
import DefaultFallback from './DefaultFallback.vue'interface Props {timeout?: number | stringfallbackMessage?: string
}withDefaults(defineProps<Props>(), {timeout: 0,fallbackMessage: '加载中...'
})const isLoading = ref(false)
const isResolved = ref(false)const onPending = () => {isLoading.value = trueisResolved.value = falseconsole.log('AsyncWrapper: 开始加载')
}const onResolve = () => {isLoading.value = falseisResolved.value = trueconsole.log('AsyncWrapper: 加载完成')
}const onFallback = () => {console.log('AsyncWrapper: 显示备用内容')
}const onError = (error: any) => {isLoading.value = falseconsole.error('AsyncWrapper: 加载错误', error)
}
</script>
<!-- 使用 AsyncWrapper -->
<template><AsyncWrapper fallback-message="用户信息加载中..."><template #default><UserProfile /></template><template #fallback><CustomLoadingSpinner /></template></AsyncWrapper>
</template>

8.2 性能优化技巧

<template><div><!-- 懒加载非关键内容 --><Suspense v-if="shouldLoadComments"><template #default><ProductComments /></template><template #fallback><CommentsSkeleton /></template></Suspense><!-- 预加载但延迟显示 --><Suspense><template #default><RelatedProducts v-if="showRelatedProducts" /></template><template #fallback><RelatedProductsSkeleton v-if="showRelatedProducts" /></template></Suspense></div>
</template><script setup lang="ts">
import { ref, onMounted } from 'vue'
import { defineAsyncComponent } from 'vue'const shouldLoadComments = ref(false)
const showRelatedProducts = ref(false)// 延迟加载评论组件
const ProductComments = defineAsyncComponent(() =>import('./ProductComments.vue')
)// 预加载相关产品组件
const RelatedProducts = defineAsyncComponent({loader: () => import('./RelatedProducts.vue'),suspensible: false // 不触发 Suspense
})onMounted(() => {// 用户滚动到评论区域时加载setTimeout(() => {shouldLoadComments.value = true}, 1000)// 延迟显示相关产品setTimeout(() => {showRelatedProducts.value = true}, 500)
})
</script>

9. 注意事项和限制

9.1 使用限制

  1. 嵌套异步:Suspense 不能捕获嵌套在同步组件中的异步操作
  2. SSR:在服务端渲染中需要特殊处理
  3. 错误边界:需要配合 errorCaptured 或 onErrorCaptured 使用

9.2 最佳实践

  1. 适当的加载状态:提供有意义的加载指示器
  2. 错误处理:始终处理可能的加载错误
  3. 性能考虑:合理使用懒加载和预加载
  4. 用户体验:考虑加载时间和失败重试机制

10. 总结

Suspense 为 Vue 3 提供了强大的异步组件处理能力:

主要优势:

  • 声明式异步处理:简化异步组件的使用
  • 更好的用户体验:提供平滑的加载状态过渡
  • 代码组织:分离加载逻辑和业务逻辑
  • 类型安全:完整的 TypeScript 支持

适用场景:

  • 路由级别的懒加载
  • 大型组件的异步加载
  • 数据获取和组件渲染的协调
  • 复杂的多步骤异步操作
http://www.dtcms.com/a/479316.html

相关文章:

  • 东莞响应式网站哪家好淘宝详情页设计模板
  • 站长之家ping检测易语言怎么用网站做背景音乐
  • phpcmsv9手机网站企业网站托管公司
  • 说一下数据库中的NULL
  • 游戏怎么做充值网站网站开发好后要做什么
  • 基于PostGIS的相邻图形方位计算,东南西北相邻计算
  • FPGA强化-串口RS485
  • 2025深圳国际传感器技术与应用展览会效果如何,有啥亮点?
  • 房产交易网站建设策划案微信网站开发技术
  • 网站域名续费怎么做网站 配色方案
  • LSTM自然语言处理情感分析项目(三)定义模型结构与模型训练评估测试
  • STM32MP1开发流程
  • 利用小偷程序做网站中国工程建设交易信息网站
  • 上海做网站公司qinmoo企业网站推广可以选择哪些方法
  • 怎么配置网站服务器网站联盟名词解释
  • 做网站需要学啥中国网站用Cn域名
  • 企业网站优化推广方法个人性质的网站
  • 美食网站建设宠物网站项目
  • 青岛做网站服务商活动网页怎么做
  • Rokid YodaOS-Master 空间渲染技术深度解析:双目立体显示与光照模拟的实现逻辑
  • ups国际快递网站建设小说网站的网编具体做哪些工作
  • 英孚教育Write Spark青少儿创新写作征集活动正式启动
  • 润商网站建设服务抖音带运营3种合作方式
  • GESP等级认证C++三级17-位运算5-2
  • 济南做网站的公司成都市建设二维码检测网站
  • 基于MATLAB的FY-3B MWRI数据处理
  • 2025年优化算法:多策略改进蛇优化算法( Improved Snake Optimizer,ISO)
  • 苹果软件混淆与 iOS 应用加固白皮书,IPA 文件加密、反编译防护与无源码混淆方案全解析
  • wordpress 建网站视频深圳网络推广
  • 做购物网站支付需要怎么做关于建筑工程的网站