useSuspense()
High performance async data rendering without overfetching.
Use await em useSuspense() nos componentes Vue. Isso significa que o restante do componente só é executado depois que os dados foram carregados, evitando a complexidade de tratar condições de carregamento e de erro. Em vez disso, o tratamento de fallback é
centralizado com o Suspense nativo do Vue.
useSuspense() reage às mutações de dados, renderizando novamente somente quando necessário.
Uso
- Rest
- Promise
<script setup lang="ts"> import { useSuspense } from '@data-client/vue'; import { ProfileResource } from './ProfileResource'; const profile = await useSuspense(ProfileResource.get, { id: 1 }); </script> <template> <div class="listItem"> <Avatar :src="profile.avatar" /> <div> <h4>{{ profile.fullName }}</h4> <p>{{ profile.bio }}</p> </div> </div> </template>
<script setup lang="ts"> import { useSuspense } from '@data-client/vue'; import { getProfile } from './Profile'; const profile = await useSuspense(getProfile, 1); </script> <template> <div class="listItem"> <Avatar :src="profile.avatar" /> <div> <h4>{{ profile.fullName }}</h4> <p>{{ profile.bio }}</p> </div> </div> </template>
Comportamento
A política de cache é Stale-While-Revalidate por padrão, mas também configurável.
| Status de expiração | Fetch | Suspend | Error | Condições |
|---|---|---|---|---|
| Inválido | sim1 | sim | não | não está na store, exclusão, invalidação, invalidIfStale |
| Desatualizado | sim1 | não | não | (primeira renderização, mudança de args) & expiração < agora |
| Válido | não | não | talvez2 | conclusão do fetch |
| não | não | não | null usado como segundo argumento |
- Fetches idênticos são automaticamente deduplicados
- Erros hard devem ser capturados por onErrorCaptured()
Usar null como segundo argumento de qualquer hook do Data Client significa "não fazer nada."
// todo could be undefined if id is undefined
const todo = await useSuspense(
TodoResource.get,
computed(() => (id.value ? { id: id.value } : null)),
);
Tipos
function useSuspense(
endpoint: ReadEndpoint,
...args: MaybeRefsOrGetters<Parameters<typeof endpoint>> | [null]
): Promise<DeepReadonly<ComputedRef<Denormalize<typeof endpoint.schema>>>>;
Os argumentos podem ser valores simples, refs (incluindo computed) ou funções getter
como () => ({ id: props.id }). Um objeto simples como { id: props.id } é lido uma única vez e não
acompanha mudanças de props ou de rota, então use um getter ou computed quando um argumento puder mudar.
O resultado é atualizado quando os argumentos mudam.
Enquanto os dados dos novos argumentos carregam, o resultado mantém os dados anteriores em vez de se tornar undefined.
Se esse fetch falhar, a leitura do resultado lança o erro (conforme sua política de erros), de modo que ele chega ao
onErrorCaptured().
Exemplos
Lista
<script setup lang="ts"> import { useSuspense } from '@data-client/vue'; import { ProfileResource } from './ProfileResource'; const profiles = await useSuspense(ProfileResource.getList); </script> <template> <div> <div class="listItem" v-for="profile in profiles" :key="profile.pk()"> <Avatar :src="profile.avatar" /> <div> <h4>{{ profile.fullName }}</h4> <p>{{ profile.bio }}</p> </div> </div> </div> </template>
Paginação
A paginação reativa é obtida com schemas mutáveis
<script setup lang="ts"> import { useSuspense } from '@data-client/vue'; import PostItem from './PostItem.vue'; import LoadMore from './LoadMore.vue'; import { PostResource } from './Post'; const data = await useSuspense(PostResource.getList); </script> <template> <div> <PostItem v-for="post in data.posts" :key="post.pk()" :post="post" /> <LoadMore v-if="data.cursor" :cursor="data.cursor" /> </div> </template>
Sequencial
Quando os parâmetros do fetch dependem de dados de outro resource.
<script setup lang="ts">
import { computed } from 'vue';
import { useSuspense } from '@data-client/vue';
import { PostResource, UserResource } from './Resources';
const props = defineProps<{ id: string }>();
const post = await useSuspense(PostResource.get, () => ({ id: props.id }));
const author = await useSuspense(UserResource.get, () => ({
id: post.value.userId,
}));
</script>
Condicional
null evita vincular e buscar os dados
<script setup lang="ts"> import { computed } from 'vue'; import { useSuspense } from '@data-client/vue'; import { PostResource, UserResource } from './Resources'; const props = defineProps<{ id: string }>(); const post = await useSuspense(PostResource.get, () => ({ id: props.id })); const author = await useSuspense( UserResource.get, computed(() => post.value.userId ? { id: post.value.userId, } : null, ), ); // author as ComputedRef<User | undefined> </script> <template> <div v-if="author"> <!-- render author --> </div> </template>
Dados incorporados
Quando as entidades são armazenadas em estruturas aninhadas, essa estrutura é mantida.
import { Entity, RestEndpoint, Collection } from '@data-client/rest'; export class PaginatedPost extends Entity { id = ''; title = ''; content = ''; static key = 'PaginatedPost'; } export const getPosts = new RestEndpoint({ path: '/post', searchParams: { page: '' }, schema: { posts: new Collection([PaginatedPost]), nextPage: '', lastPage: '', }, });
<script setup lang="ts"> import { useSuspense } from '@data-client/vue'; import { getPosts } from './api/Post'; const props = defineProps<{ page: string }>(); const data = await useSuspense(getPosts, () => ({ page: props.page })); </script> <template> <div> <div v-for="post in data.posts" :key="post.pk()">{{ post.title }}</div> </div> </template>