Todos nós tivemos a frustração de aguardar através de telas de carregamento longas, apenas para descobrirmos que estamos presos com páginas inativas. Você vê spinner de carregamento em todo lugar, mas nada parece avançar. Deixe-me pintar uma imagem mais clara para você:

Isso normalmente acontece porque o site está tentando obter todos os dados necessários assim que você chegar na página. Pode ser que uma solicitação API esteja sendo processada ou que várias APIs estejam obtendo dados seqüencialmente, causando atrasos no carregamento da página.

O resultado? Uma má experiência do usuário. Você pode pensar: “Como é que uma empresa tão grande não prioriza a experiência do usuário? Isto é decepcionante.” Consequentemente, os usuários frequentemente deixam o site, afetando as métricas chave e potencialmente impactando o rendimento.

Mas e se você pudesse obter os dados para essas páginas pesadas antes do tempo, para que quando o usuário chegar na página, eles possam interagir com ela instantaneamente?

É aqui que entra o conceito de prefetching, e é exactamente sobre isso que vamos mergulhar neste post de blog. Então, sem perda de tempo, vamos começar!

Sumário

Prefetching como Solução

Aqui está a versão revisada apenas com as correções de gramática e ortografia:

Para o problema acima, o que queremos é buscar os dados de uma página dada antes de ela ser carregada no site, para que o usuário não precise buscar os dados na carga da página. Isso é chamado de prefetching. De forma técnica, sua definição é a seguinte:

É uma maneira de buscar os dados necessários antecipadamente, para que o componente principal não precise esperar pelos dados, melhorando assim a experiência.

Isso pode melhorar a experiência do usuário, aumentando a confiança do cliente no seu site.

O prefetching é uma solução simples e elegante que é mais usuário-centrada do que um processo padrão. Para implementar o prefetching, precisamos entender o comportamento do usuário no site. Isso é, as páginas mais visitadas ou quais componentes buscam dados em pequenas interações (como passar o mouse).

Após analisar esses cenários, faz sentido aplicar prefetching neles. No entanto, como desenvolvedores, devemos ter cuidado ao usar esse conceito. Muito prefetching também pode atrasar seu site, já que você está tentando buscar muitos dados para cenários futuros, o que pode bloquear a busca de dados para a página principal.

Como o Prefetching Melhora a Experiência do Usuário

Vamos olhar para alguns cenários onde o prefetching é benéfico:

  1. Carregar dados/página mais cedo para o link mais visitado da sua página de destino. Por exemplo, considere que você tem um link “contate-nos”. Vamos supor que este é o link que os usuários conferem com mais frequência e contém muitos dados quando é carregado. Em vez de carregar os dados quando a página de contato for carregada, você pode simplesmente buscar os dados na página inicial para que não seja necessário esperar na página Contate-Nos pelos dados. Você pode ler mais sobre prefetching de páginas aqui.

  2. Carregar previamente dados de tabela para páginas posteriores.

  3. Recuperação de dados the um componente pai e carregamento no componente filho.

  4. Prefetching de dados que precisam ser exibidos numa popover.

Estes são alguns dos modos de alcançar o prefetching na sua aplicação e como isso ajuda a melhorar a experiência do utilizador.

Neste artigo de blog iremos discutir sobre o último cenário: prefetching de dados que precisam ser exibidos na popover”. Isto é um exemplo clássico onde o prefetching pode ser benéfico e fornece uma experiência mais fluida ao utilizador.

Entender o Problema

Deixe-me definir o problema aqui. Imaginem o seguinte cenário:

  1. Tem um componente que exibe informações específicas.

  2. Há um elemento dentro deste componente que mostra outra popover/tooltip quando você passa o mouse sobre ele.

  3. A popover recupera dados quando carrega.

Agora imaginem que o usuário passa o mouse sobre o elemento e precisa esperar para que os dados sejam recuperados e exibidos na popover. Durante esse tempo de espera, eles veem o carregador de esqueleto.

O cenário parecerá com isso:

É muito frustante ver quão longo o usuário tem que esperar sempre que passa o mouse sobre a imagem:

Para resolver esse problema, existem duas soluções que podem ajudar você a começar e a personalizar a solução de acordo com suas necessidades.

Solução #1: Pré-carregar Dados no Componente Pai

Esta solução é inspirada em um post do blog de Martin Fowler. Permite que você carregue os dados antes da popup aparecer, em vez de carregá-los no momento do carregamento do componente.

A popup aparece quando você passa o mouse sobre ela. Nós podemos carregar os dados quando o mouse entra no componente pai. Antes mesmo de o componente real — a imagem — ser passado com o mouse, temos os dados para a popover e os passamos para o componente popover.

Esta solução não remove completamente o estado de carregamento, mas ajuda significativamente a reduzir as chances de ver o estado de carregamento.

Solução #2: Pré-carregar Dados na Pagina de Carregamento

Esta solução é inspirada em x.com onde, para o componente popover, eles carregam parcialmente os dados na página principal de carregamento e carregam o resto dos dados quando o componente monta.

Como você pode ver no vídeo acima, os detalhes do perfil do usuário são exibidos na popover. Se você olhar de perto, os detalhes relacionados aos seguidores são carregados posteriormente.

Esta técnica é altamente eficiente quando você tem muitos dados para serem exibidos na caixa de diálogo, mas buscá-los pode ser caro no momento do carregamento da caixa de diálogo ou no carregamento da página principal.

Uma solução melhor seria carregar parcialmente os dados necessários na página principal e carregar o restante dos dados quando o componente for montado.

No nosso exemplo, buscámos os dados para a caixa de diálogo quando o cursor entrou no elemento pai da imagem. Agora imaginem que você precisa buscar detalhes adicionais assim que os dados da caixa de diálogo forem carregados. Então, com base na metodologia do x.com acima, podemos buscar dados adicionais quando a caixa de diálogo for carregada. Aqui está o resultado disso:

Neste caso, fazemos as seguintes coisas:

  • Nós buscamos os dados principais que são necessários apenas para renderizar a caixa de diálogo quando o mouse entra no componente pai da imagem.

  • Isto dá tempo suficiente para buscar os dados principais.

  • Ao carregar a caixa de diálogo, buscamos outros dados, que é o número de álbuns. Enquanto o usuário lê dados como nome e e-mail, terá os próximos dados prontos para serem vistos.

Dessa forma, podemos fazer pequenas e inteligentes melhorias para minimizar o olhar vazio dos carregadores na tela 😊.

Como Implementar o Prefetching com React

Nesta seção, passaremos brevemente pelo como implementar o exemplo de aplicação de pré-carregamento acima.

Configuração do Projeto

Para começar a criar a aplicação de pré-carregamento, siga com o processo abaixo:

Pode usar vitejs (isto é o que eu usei) ou create-react-app para criar a sua aplicação. Copie o comando abaixo no seu terminal:

yarn create vite prefetch-example --template react-ts

Uma vez criada a aplicação, deveria ter a seguinte estrutura de pastas quando abrir o diretório prefetch-example no VS Code.

Agora vamos mergulhar nos componentes que vamos construir para esta aplicação.

Componentes

Neste exemplo, vamos usar 3 componentes:

  • PopoverExample

  • UserProfile

  • UserProfileWithFetching

PopoverExample Componente

Vamos começar com o primeiro componente, que é o PopoverExample. Este componente exibe uma imagem de avatar e algum texto à direita dela. Deveria parecer assim:

O objetivo deste componente é servir como um exemplo semelhante a situações reais da vida. A imagem neste componente carrega um componente de popover quando é passado sobre.

Aqui está o código do componente:

import { useState } from "react";
import { useFloating, useHover, useInteractions } from "@floating-ui/react";
import ContentLoader from "react-content-loader";
import UserProfile from "./UserProfile";
import UserProfileWithFetching from "./UserProfileWithFetching";

export const MyLoader = () => (
    <ContentLoader
        speed={2}
        width={340}
        height={84}
        viewBox="0 0 340 84"
        backgroundColor="#d1d1d1"
        foregroundColor="#fafafa"
    >
        <rect x="0" y="0" rx="3" ry="3" width="67" height="11" />
        <rect x="76" y="0" rx="3" ry="3" width="140" height="11" />
        <rect x="127" y="48" rx="3" ry="3" width="53" height="11" />
        <rect x="187" y="48" rx="3" ry="3" width="72" height="11" />
        <rect x="18" y="48" rx="3" ry="3" width="100" height="11" />
        <rect x="0" y="71" rx="3" ry="3" width="37" height="11" />
        <rect x="18" y="23" rx="3" ry="3" width="140" height="11" />
        <rect x="166" y="23" rx="3" ry="3" width="173" height="11" />
    </ContentLoader>
);
export default function PopoverExample() {
    const [isOpen, setIsOpen] = useState(false);
    const [isLoading, setIsLoading] = useState(false);
    const [data, setData] = useState({});

    const { refs, floatingStyles, context } = useFloating({
        open: isOpen,
        onOpenChange: setIsOpen,
        placement: "top",
    });

    const hover = useHover(context);

    const { getReferenceProps, getFloatingProps } = useInteractions([hover]);

    const handleMouseEnter = () => {
        if (Object.keys(data).length === 0) {
            setIsLoading(true);
            fetch("https://jsonplaceholder.typicode.com/users/1")
                .then((resp) => resp.json())
                .then((data) => {
                    setData(data);
                    setIsLoading(false);
                });
        }
    };

    return (
        <div
            id="hover-example"
            style={{
                display: "flex",
                flexDirection: "row",
                alignItems: "center",
                textAlign: "left",
            }}
            onMouseEnter={handleMouseEnter}
        >
            <span
                style={{
                    padding: "1rem",
                }}
            >
                <img
                    ref={refs.setReference}
                    {...getReferenceProps()}
                    style={{
                        borderRadius: "50%",
                    }}
                    src="https://cdn.jsdelivr.net/gh/alohe/avatars/png/vibrent_5.png"
                />
            </span>
            <p>
                Lorem Ipsum is simply dummy text of the printing and typesetting
                industry. Lorem Ipsum has been the industry's standard dummy text ever
                since the 1500s, when an unknown printer took a galley of type and
                scrambled it to make a type specimen book. It has survived not only five
                centuries, but also the leap into electronic typesetting, remaining
                essentially unchanged. It was popularised in the 1960s with the release
                of Letraset sheets containing Lorem Ipsum passages, and more recently
                with desktop publishing software like Aldus PageMaker including versions
                of Lorem Ipsum.
            </p>
            {isOpen && (
                <div
                    className="floating"
                    ref={refs.setFloating}
                    style={{
                        ...floatingStyles,
                        backgroundColor: "white",
                        color: "black",
                        padding: "1rem",
                        fontSize: "1rem",
                    }}
                    {...getFloatingProps()}
                >
                    {isLoading ? (
                        <MyLoader />
                    ) : (
                        <UserProfile hasAdditionalDetails {...data} />
                    )}
                    {/* <UserProfileWithFetching /> */}
                </div>
            )}
        </div>
    );
}

Há algumas coisas acontecendo aqui, deixe-me explicar passo a passo:

  • Temos um div pai chamado hover-example que contém uma imagem e algum texto.

  • A seguir, nós renderizamos condicionalmente um div com o nome de classe floating. Este é o componente de popover real que abre quando você passa o mouse sobre a imagem.

  • Dentro do popover, nós carregamos condicionalmente o UserProfile e o carregador de esqueleto. Este carregador aparece quando estamos buscando dados para o perfil do usuário. Mais sobre isso depois.

  • Usamos a biblioteca react-content-loader no componente MyLoader. Esta biblioteca também tem um site que ajuda você a criar carregadores, você pode ver aqui.

Componente UserProfile

Agora que definimos o exemplo de Popover, é hora de entender os detalhes do componente UserProfile.

Este componente aparece dentro do componente popover. O propósito deste componente é carregar os detalhes name email phone website, que são buscados a partir do JSON placeholder API.

Para demonstrar o exemplo de prefetching, temos que garantir que o componente UserProfile age apenas como um componente de apresentação; isto é, não há nenhuma lógica de busca explícita presente nele.

O ponto chave a notar sobre este componente é que a busca de dados acontece do componente pai, que é o componente PopoverExample. Neste componente, começamos a buscar dados quando o mouse entra neste componente (o evento mouseenter). Esta é a solução #1 que discutimos anteriormente.

Isso dá tempo suficiente para buscar dados até o usuário passar o mouse sobre a imagem. Aqui está o código:

import { useEffect, useState } from "react";
import ContentLoader from "react-content-loader";

const MyLoader = () => (
    <ContentLoader
        speed={2}
        viewBox="0 0 476 124"
        backgroundColor="#d1d1d1"
        foregroundColor="#fafafa"
    >
        <rect x="4" y="43" rx="0" ry="0" width="98" height="30" />
    </ContentLoader>
);

export default function UserProfile(props: Record<string, string | boolean>) {
    const { name, email, phone, website, hasAdditionalDetails } = props;
    const [isLoading, setIsLoading] = useState(false);
    const [additionalData, setAdditionalData] = useState(0);

    useEffect(() => {
        if (hasAdditionalDetails) {
            setIsLoading(true);
            fetch("https://jsonplaceholder.typicode.com/albums")
                .then((resp) => resp.json())
                .then((data: Array<unknown>) => {
                    const albumCount = data.reduce((acc, curr) => {
                        if (curr.userId === 1) acc += 1;

                        return acc;
                    }, 0);
                    setAdditionalData(albumCount);
                })
                .finally(() => {
                    setIsLoading(false);
                });
        }
    }, [hasAdditionalDetails]);

    return (
        <div id="user-profile">
            <div id="user-name">name: {name}</div>
            <div id="user-email">email: {email}</div>
            <div id="user-phone">phone: {phone}</div>
            <div id="user-website">website: {website}</div>
            {hasAdditionalDetails && (
                <>
                    {isLoading ? (
                        <MyLoader />
                    ) : (
                        <div id="user-albums">Album Count: {additionalData}</div>
                    )}
                </>
            )}
        </div>
    );
}

Este componente faz uso da propriedade hasAdditionalDetails. O objetivo desta prop é carregar dados adicionais quando o componente é montado. Ela ilustra a solução #2 mencionada acima.

Componente UserProfileWithFetching

Este componente é muito semelhante ao componente UserProfile. Ele contém apenas a lógica para buscar dados quando o componente é carregado. O objetivo deste componente é mostrar como a solução geral sem a técnica de prefetching iria parecer.

Então, este componente sempre carregará os dados quando o componente for montado, o que exibe o carregador de esqueleto.

Aqui está o código:

import { useEffect, useState } from "react";
import { MyLoader } from "./PopoverExample";

export default function UserProfileWithFetching() {
    const [isLoading, setIsLoading] = useState(false);
    const [data, setData] = useState<Record<string, string>>({});

    useEffect(() => {
        setIsLoading(true);
        fetch("https://jsonplaceholder.typicode.com/users/1")
            .then((resp) => resp.json())
            .then((data) => {
                setData(data);
                setIsLoading(false);
            });
    }, []);

    if (isLoading) return <MyLoader />;

    return (
        <div id="user-profile">
            <div id="user-name">name: {data.name}</div>
            <div id="user-email">email: {data.email}</div>
            <div id="user-phone">phone: {data.phone}</div>
            <div id="user-website">website: {data.website}</div>
        </div>
    );
}

O código inteiro deste aplicativo pode ser encontrado aqui.

Muito prefetching também pode causar lentidão

Um aviso, muito prefetching não é bom porque:

  • Pode lentificar sua aplicação.

  • Isso pode degradar a experiência do usuário se o preenchimento não for aplicado estratégicamente.

O preenchimento precisa ser aplicado quando você sabe o comportamento do usuário. Isso é, você é capaz de prever o movimento do usuário por meio de métricas e saber se eles visitam uma página frequentemente. Nesse caso, o preenchimento é uma boa ideia.

Então, lembre-se de sempre aplicar o preenchimento estratégicamente.

Resumo

Essa é tudo, pessoal! Espero que você goste do meu post no blog. Neste post no blog, você aprendeu que a implementação de preenchimento pode melhorar significativamente a velocidade e a resposta de sua aplicação web, melhorando a satisfação do usuário.

Para ler mais, consulte os artigos abaixo:

Para mais conteúdo, você pode seguir comigo em Twitter, GitHub, e LinkedIn.