Pular para o conteúdo principal

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​

<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>

Comportamento​

A política de cache é Stale-While-Revalidate por padrão, mas também configurável.

Status de expiraçãoFetchSuspendErrorCondições
Inválidosim1simnãonão está na store, exclusão, invalidação, invalidIfStale
Desatualizadosim1nãonão(primeira renderização, mudança de args) & expiração < agora
Válidonãonãotalvez2conclusão do fetch
nãonãonãonull usado como segundo argumento
observação
  1. Fetches idênticos são automaticamente deduplicados
  2. Erros hard devem ser capturados por onErrorCaptured()
Dependências condicionais

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

▶Resources
▶PostWithAuthor.vue
<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.

▶api/Post
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: '',
  },
});
▶ArticleList.vue
<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>