setup 不能是 async 函数,因为会返回 Promise 导致模板无法访问响应式数据、生命周期钩子失效;正确方式是用 defineAsyncComponent 或 <script setup> 顶层 await(Vue 3.4+),或在组合函数中分离 load 方法手动调用。

不能在 setup 顶层直接用 async 函数,因为这会让 setup 返回一个 Promise,模板无法访问其中的响应式变量——Vue 会把整个组件当作“异步组件”处理,但此时 <Suspense> 并不会自动生效,除非你显式把它变成一个异步组件。
为什么 setup 不能是 async 函数
setup 是 Composition API 的入口,它的返回值必须是一个对象(供模板使用),或一个渲染函数。一旦标记为 async,返回值就变成 Promise<object>,Vue 模板系统无法解包这个 Promise,导致数据不可见、绑定失效,甚至生命周期钩子(如 onMounted)注册失败、引发内存泄漏。
正确配合 Suspense 的方式:用 defineAsyncComponent
要让 <Suspense> 生效,必须让组件本身是“异步加载”的,而不是在 setup 内部做 await。推荐做法是把需要异步初始化的逻辑封装进一个子组件,再用 defineAsyncComponent 动态导入它:
- 父组件中用
<Suspense>包裹该子组件 - 子组件自身使用普通
setup,在onMounted或组合函数中触发异步操作(如useProfile(id).load()) - 子组件内部管理 loading 状态、错误状态,或通过
v-if控制内容显示
替代方案:顶层 await(仅限 .vue 单文件组件)
Vue 3.4+ 支持在 <script setup> 中使用顶层 await,但需满足两个条件:
- 脚本必须是
<script setup lang="ts">或lang="js" - 组件会被自动包装为异步组件,
<Suspense>可捕获其加载过程
示例:
<script setup lang="ts">const data = await api.fetchUserProfile()
</script>
此时 Vue 编译器会自动将其转为 defineAsyncComponent 形式,<Suspense> 能正常接管 fallback 显示。
组合函数里不要 await,只构造可调用的 load 方法
像 useProfile 这类组合函数,应避免在 setup 里 await useProfile(id).load(),而应:
- 在 setup 中调用
const { profile, load } = useProfile(id) - 在
onMounted或用户交互时手动执行load() - 配合
v-if="$pending"或状态字段控制 UI,而非依赖 Suspense
这样更可控,也兼容非 Suspense 场景(比如 SSR 或微前端子应用)。


















