Pular para o conteúdo principal

Render as you Fetch

Uma característica central do design do Reactive Data Client é desacoplar a obtenção real dos dados do uso dos dados. Isso significa que hooks que querem garantir a disponibilidade dos dados, como useFetch() ou useSuspense(), na verdade apenas despacham a requisição de fetch. O NetworkManager então usa sua visão global para determinar se deve fazer o fetch. Isso significa, por exemplo, que requisições duplicadas de dados podem ser deduplicadas em um único fetch, com uma única promise a resolver.

Outra implicação interessante é que fetches iniciados de forma imperativa via Controller.fetchIfStale() e Controller.fetch() não resultarão em fetches redundantes. Isso é conhecido como 'fetch as you render' e frequentemente resulta em uma experiência de usuário melhor.

Estes são alguns cenários em que esse padrão é especialmente útil:

O fetch-as-you-render pode ser adotado de forma incremental. Componentes que usam dados podem usar useSuspense() e ter certeza de que receberão seus dados quando estiverem prontos. E quando otimizações de render-as-you-fetch forem adicionadas depois - esses componentes não precisam mudar. Isso torna o uso dos dados fortemente acoplado ao componente, e a otimização do fetch fracamente acoplada.

Rotas que fazem preload​

Na maioria dos casos, o melhor momento para fazer o prefetch dos dados é na camada de roteamento. Fazer isso torna bastante fácil incorporar todas as capacidades acima.

Use Controller.fetchIfStale no handler de eventos da rota (antes de startTransition)

import { Controller } from '@data-client/react';
import { lazy, Route } from '@anansi/router';
import { getImage } from '@data-client/img';

import { lazyPage } from './lazyPage';
import { PostResource } from '@/resources/Post';
import { UserResource } from '@/resources/User';

export const routes: Route<Controller>[] = [
{
name: 'UserDetail',
component: lazyPage('UserDetail'),
resolveData: async (controller: Controller, match: { id: string }) => {
if (match) {
const fakeUser = UserResource.fromJS({
id: Number.parseInt(match.id, 10),
});
// don't block on posts but start fetching
controller.fetchIfStale(PostResource.getList, { userId: match.id });
await Promise.all([
controller.fetchIfStale(UserResource.get, match),
controller.fetchIfStale(getImage, {
src: fakeUser.profileImage,
}),
controller.fetchIfStale(getImage, {
src: fakeUser.coverImage,
}),
controller.fetchIfStale(getImage, {
src: fakeUser.coverImageFallback,
}),
]);
}
},
},
];

Componentes que usam dados​

UserDetail page

import { useSuspense } from '@data-client/react';
import { Img } from '@data-client/img';
import { Card, Avatar } from 'antd';

import { UserResource } from 'resources/Discuss';
import Boundary from 'Boundary';
import PostList from 'pages/Posts';

export type Props = { id: string };
const { Meta } = Card;

export default function UserDetail({ id }: Props) {
const user = useSuspense(UserResource.get, { id });
return (
<>
<Card cover={<Img src={user.coverImage} />}>
<Meta
avatar={<Img component={Avatar} src={user.profileImage} size={64} />}
title={user.name}
description={
<>
<div>{user.website}</div>
<div>{user.company.catchPhrase}</div>
</>
}
/>
</Card>
<Boundary fallback={<CardLoading />}>
<PostList userId={user.pk()} />
</Boundary>
</>
);
}
export function CardLoading() {
return <Card style={{ marginTop: 16 }} loading={true} />;
}