异步组件

在实际开发中,组件经常需要在初始化时加载异步数据(如请求接口)。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 的属性

属性类型说明
childrenView实际要渲染的内容
fallbackView加载中显示的占位视图
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>
  )
}

下一步