Pular para o conteúdo principal

Renderizando dados assíncronos

Torne seus componentes reutilizáveis associando os dados onde você os usa, com o useSuspense() de uma linha, que garante os dados com 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>
Endpoints usados em muitos contextosEndpoints usados em muitos contextos

Não faça prop drilling. Em vez disso, use useSuspense() nos componentes que renderizam os dados. Isso é conhecido como data co-location (colocalização de dados).

Não esconda hooks de associação de dados dentro de hooks personalizados. Em vez disso, coloque transformações de dados fortemente acopladas em Query: a lógica de dados pertence ao modelo de dados, onde permanece visível, reutilizável e livre para mudar independentemente da view.

Em vez de escrever funções de atualização complexas ou cascatas de invalidações, o Reactive Data Client atualiza automaticamente os componentes associados imediatamente após uma alteração nos dados. Isso é conhecido como programação reativa.

Carregamento e erro​

Você pode ter notado que o tipo de retorno mostra que o valor sempre está presente. useSuspense() funciona de forma muito parecida com await. Isso nos permite separar o tratamento de erro/carregamento do uso dos dados.

Limites assíncronos (Async Boundaries)​

Em vez disso, colocamos o <Suspense /> nativo do Vue junto com onErrorCaptured() para tratar as condições de carregamento e de erro em limites de navegação, ou acima deles, como páginas, rotas ou modais.

Dashboard.vue
<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>

Centralizar os fallbacks dessa forma elimina indicadores de carregamento redundantes, mantendo os componentes reutilizáveis. O fallback de carregamento é personalizado com o slot #fallback do <Suspense />, e o fallback de erro renderizando o que você escolher a partir de onErrorCaptured().

Com estado (Stateful)​

Você pode encontrar casos em que ainda é útil usar uma abordagem com estado para os fallbacks. Para esses casos, ou para compatibilidade com algumas bibliotecas de componentes, há o useDLE() - [D]ata [L]oading [E]rror (dados, carregamento e erro).

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

Como useDLE não usa useSuspense, você não conseguirá orquestrar facilmente de forma centralizada o código de carregamento e de erro.

Condicional​

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)),
);

Subscriptions​

Quando é provável que os dados mudem por fatores externos, useSubscription() garante atualizações contínuas enquanto um componente está montado. useLive() chama tanto useSubscription() quanto useSuspense(), facilitando bastante o uso de dados atualizados.

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

As subscriptions são orquestradas por Managers. Por padrão, subscriptions baseadas em polling podem ser usadas adicionando pollFrequency a um Endpoint ou Resource. Para protocolos de rede baseados em push, como SSE e websockets, veja o exemplo de manager de stream.

export const getTicker = new RestEndpoint({
urlPrefix: 'https://api.exchange.coinbase.com',
path: '/products/:productId/ticker',
schema: Ticker,
pollFrequency: 2000,
});