渲染异步数据
只需一行 useSuspense(),就能在使用数据的地方绑定数据,让你的组件可复用;它能 配合 await 一样保证数据的存在。
<script setup lang="ts"> import { useSuspense } from '@data-client/vue'; import PostItem from './PostItem.vue'; import { PostResource } from './Resources'; const emit = defineEmits<{ setRoute: [route: string] }>(); const posts = await useSuspense(PostResource.getList); </script> <template> <div> <PostItem v-for="post in posts" :key="post.pk()" :post="post" @setRoute="emit('setRoute', $event)" /> </div> </template>

不要进行 prop 逐层传递。而是在渲染数据的组件中直接调用 useSuspense()。这被称为 数据就近放置(data co-location)。
不要把数据绑定 hook 藏在自定义 hook 中。而应把紧密耦合的数据转换放在 Query 中——数据逻辑应当与数据模型放在一起,这样它保持可见、可复用,并且可以独立于视图自由修改。
Reactive Data Client 会在数据变化时立即自动更新绑定的组件,无需编写复杂的更新函数或级联失效逻辑。这被称为 响应式编程。
加载与错误
你可能已经注意到,返回类型表明这个值总是存在的。useSuspense() 的工作方式非常配合 await。这让我们可以把错误和加载的处理与数据的使用分离开来。
异步边界
我们会在**页面、路由或模态框**等导航边界处或其上层放置 Vue 内置的 <Suspense /> 以及 onErrorCaptured(),用来处理加载和错误状态。
<script setup lang="ts">
import { onErrorCaptured, ref } from 'vue';
const error = ref<Error | null>(null);
onErrorCaptured(err => {
error.value = err;
return false;
});
</script>
<template>
<div>
<h1>Dashboard</h1>
<section>
<div v-if="error">Error: {{ error.message }}</div>
<Suspense v-else>
<template #default>
<RouterView />
</template>
<template #fallback>
<Loading />
</template>
</Suspense>
</section>
</div>
</template>
以这种方式集中管理 fallback,可以在保持组件可复用的同时消除冗余的加载指示器。加载 fallback 通过 <Suspense /> 的 #fallback 插槽定制,错误 fallback 则通过渲染你在 onErrorCaptured() 中选择的内容来定制。
有状态的方式
在某些情况下,你可能会发现使用有状态的方式处理 fallback 仍然有用。对于这些情况,或者为了兼容某些组件库,我们提供了 useDLE() - [D]ata [L]oading [E]rror。
<script setup lang="ts"> import { useDLE } from '@data-client/vue'; import { ProfileResource } from './ProfileResource'; const { data, loading, error } = useDLE(ProfileResource.getList); </script> <template> <div v-if="error">Error {{ error.status }}</div> <Loading v-else-if="loading || !data" /> <div v-else> <div class="listItem" v-for="profile in data" :key="profile.pk()"> <Avatar :src="profile.avatar" /> <div> <h4>{{ profile.fullName }}</h4> <p>{{ profile.bio }}</p> </div> </div> </div> </template>
由于 useDLE 不使用 useSuspense,你将无法轻松地集中编排加载和错误处理的 代码。
条件获取
将 null 作为任意 Data Client hook 的第二个参数,表示“什么都不做”。
// todo could be undefined if id is undefined
const todo = await useSuspense(
TodoResource.get,
computed(() => (id.value ? { id: id.value } : null)),
);
订阅
当数据可能因外部因素而发生变化时,useSubscription() 可以确保组件挂载期间持续更新。useLive() 会同时调用 useSubscription() 和 useSuspense(),让你能够非常轻松地使用最新数据。
<script setup lang="ts"> import { useLive } from '@data-client/vue'; import { getTicker } from './Ticker'; import NumberFlow from '@number-flow/vue'; const props = defineProps<{ productId: string }>(); const ticker = await useLive(getTicker, () => ({ productId: props.productId })); </script> <template> <div style="text-align: center"> {{ productId }} <NumberFlow :value="ticker.price" :format="{ style: 'currency', currency: 'USD' }" /> </div> </template>
订阅由 Managers 编排。开箱即用地,只需在 Endpoint 或 Resource 上添加 pollFrequency,就可以使用基于轮询的订阅。对于 SSE 和 websockets 这类基于推送的网络协议,请参阅数据流 manager 示例。
export const getTicker = new RestEndpoint({
urlPrefix: 'https://api.exchange.coinbase.com',
path: '/products/:productId/ticker',
schema: Ticker,
pollFrequency: 2000,
});