异步组件
在实际开发中,组件经常需要在初始化时加载异步数据(如请求接口)。Vitarx 通过 onInit 返回 Promise 的方式实现异步初始化,并配合 Suspense 组件提供加载状态的展示。
onInit 返回 Promise
当 onInit 的回调函数返回一个 Promise 时,外层的 Suspense 会等待该 Promise 完成后再显示组件内容:
tsx
import { ref, onInit } from 'vitarx'
function UserProfile(props: { userId: string }) {
const user = ref<{ name: string; avatar: string } | null>(null)
onInit(async () => {
// 返回 Promise,Suspense 会等待
const res = await fetch(`/api/user/#123;props.userId}`)
user.value = await res.json()
})
// onInit 的 Promise 完成后才会显示此内容
return (
<div>
<img src={user.value?.avatar} />
<p>{user.value?.name}</p>
</div>
)
}Suspense 配合异步组件
Suspense 是 Vitarx 内置的异步组件容器,它会在子组件的异步操作完成前显示 fallback 内容,完成后自动切换为真实内容。
tsx
import { Suspense } from 'vitarx'
function App() {
return (
<Suspense fallback={<div class="loading">加载中...</div>}>
<UserProfile userId="123" />
</Suspense>
)
}Suspense 的属性
| 属性 | 类型 | 说明 |
|---|---|---|
children | View | 实际要渲染的内容 |
fallback | View | 加载中显示的占位视图 |
onResolved | () => void | 异步加载完成时的回调 |
tsx
import { Suspense } from 'vitarx'
function App() {
return (
<Suspense
fallback={<div>加载中...</div>}
onResolved={() => {
console.log('内容加载完成!')
}}
>
<AsyncComponent />
</Suspense>
)
}多层 Suspense 嵌套
Suspense 支持嵌套使用。内层 Suspense 只等待自己包裹的异步组件,外层 Suspense 等待所有子组件(包括内层 Suspense 中的异步组件):
tsx
import { Suspense, ref, onInit } from 'vitarx'
function AsyncHeader() {
const title = ref('')
onInit(async () => {
const res = await fetch('/api/header')
title.value = (await res.json()).title
})
return <header>{title}</header>
}
function AsyncContent() {
const content = ref('')
onInit(async () => {
const res = await fetch('/api/content')
content.value = (await res.json()).content
})
return <main>{content}</main>
}
function AsyncSidebar() {
const items = ref<string[]>([])
onInit(async () => {
const res = await fetch('/api/sidebar')
items.value = (await res.json()).items
})
return (
<aside>
{items.value.map((item, i) => (
<p key={i}>{item}</p>
))}
</aside>
)
}
function App() {
return (
// 外层 Suspense 等待所有异步组件完成
<Suspense fallback={<div>页面加载中...</div>}>
<AsyncHeader />
<div class="layout">
{/* 内层 Suspense 只等待侧边栏 */}
<Suspense fallback={<div>侧边栏加载中...</div>}>
<AsyncSidebar />
</Suspense>
{/* 内容区独立等待 */}
<Suspense fallback={<div>内容加载中...</div>}>
<AsyncContent />
</Suspense>
</div>
</Suspense>
)
}异步数据加载模式
下面是一个常见的异步数据加载模式——在 onInit 中请求数据,配合 Suspense 展示加载状态:
tsx
import { ref, onInit, Suspense } from 'vitarx'
// 通用的异步数据加载组件
function DataLoader<T>(props: { url: string; children: (data: T) => any }) {
const data = ref<T | null>(null)
onInit(async () => {
const res = await fetch(props.url)
data.value = await res.json()
})
// data 在 onInit 完成后一定有值
return props.children(data.value!)
}完整示例
下面是一个完整的用户列表页面示例,展示异步组件的实际应用:
tsx
import { ref, onInit, Suspense } from 'vitarx'
// 用户信息组件 — 异步加载单个用户
function UserCard(props: { userId: string }) {
const user = ref<{ id: string; name: string; email: string } | null>(null)
onInit(async () => {
const res = await fetch(`/api/users/#123;props.userId}`)
user.value = await res.json()
})
return (
<div class="user-card">
<h3>{user.value?.name}</h3>
<p>{user.value?.email}</p>
</div>
)
}
// 用户列表组件 — 异步加载用户列表
function UserList() {
const users = ref<Array<{ id: string; name: string }>>([])
onInit(async () => {
const res = await fetch('/api/users')
const data = await res.json()
users.value = data.list
})
return (
<div class="user-list">
{users.value.map((user) => (
<UserCard key={user.id} userId={user.id} />
))}
</div>
)
}
// 页面组件
function UserPage() {
return (
<div class="page">
<h1>用户管理</h1>
<Suspense
fallback={
<div class="loading">
<p>正在加载用户数据,请稍候...</p>
</div>
}
onResolved={() => {
console.log('用户数据加载完成')
}}
>
<UserList />
</Suspense>
</div>
)
}