Tutti noi abbiamo sperimentato la frustrazione di aspettare davanti a schermate di caricamento lunghe, solo per scoprire di rimanere bloccati con pagine non reattive. Vedete spinner di caricamento ovunque, ma nulla sembra muoversi in avanti. Permettetemi di descrivere una situazione più chiara:

Questo accade generalmente perché il sito web cerca di recuperare tutti i dati necessari appena si arriva alla pagina. Potrebbe essere che una richiesta API è in fase di processamento, o che molte API recuperino i dati in sequenza, causando rallentamenti nell’caricamento della pagina.

Il risultato? Una pessima esperienza utente. Forse pensate, “Come può una società così grande non dare priorità all’esperienza utente? È deludente.” Di conseguenza, gli utenti spesso lasciano il sito, influendo sui numeri chiave e potenzialmente sulla ricchezza.

Ma cosa succederebbe se potessi recuperare i dati per queste pagine pesanti prima di tempo, cosicché appena un utente arriva sulla pagina, può interagire con essa istantaneamente?

Ecco dove entra in gioco il concetto di prefetching, e questo è esattamente ciò che approfondiremo in questo articolo del blog. Quindi senza ulteriori indugi, spiegiamo subito!

Indice

Il prefetching come soluzione

Ecco la versione rivista con correzioni solo di grammatica e ortografia:

Per il problema descritto, ciò che vogliamo è prelevare i dati per una pagina specifica prima che questa venga caricata sul sito web, in modo che l’utente non debba prelevare i dati all’avvio della pagina. Questo si chiama prefetching. Da un punto di vista tecnico, la sua definizione è la seguente:

È un metodo per prelevare i dati necessari in anticipo in modo che il componente principale non debba attendere i dati, migliorando così l’esperienza utente.

Questo può migliorare l’esperienza utente, aumentando la fiducia del cliente nel tuo sito web.

Il prefetching è una semplice ma elegante soluzione che è più user-centrica di un processo standard. Per implementare il prefetching, dobbiamo comprendere il comportamento utente sul sito web. Cioè, le pagine più visitate o quali componenti prelevano dati in piccole interazioni (come l’hover).

Dopo aver analizzato questi scenari, ha senso applicare il prefetching ad essi. Tuttavia, come sviluppatori, dovremmo essere consapevoli dell’uso di questo concetto. Troppo prefetching può anche rallentare il tuo sito web dal momento che stai cercando di recuperare molti dati per scenari futuri, il che potrebbe bloccare il recupero dei dati per la pagina principale.

Come il Prefetching migliora l’esperienza utente

Consideriamo alcuni scenari in cui il prefetching è vantaggioso:

  1. Caricamento dati/pagina anticipato per il link più visitato dalla tua pagina di destinazione. Ad esempio, immagina di avere un link “contattaci”. Immaginiamo che questo sia il link che gli utenti controllano principalmente e che contenga molti dati al momento del caricamento. Invece di caricare i dati quando la pagina contattaci si carica, puoi semplicemente recuperare i dati nella homepage in modo da non dover aspettare sulla pagina Contattaci i dati. Puoi leggere di più sul prefetching delle pagine qui.

  2. Prefetching dati tabellari per le pagine successive.

  3. Recupero dati da un componente genitore e caricamento del componente figlio.

  4. Preload dei dati da visualizzare in un tooltip.

Queste sono alcune delle modi per realizzare il preload nella vostra applicazione e come ciò aiuti a migliorare l’esperienza utente.

Nel presente articolo博客, viene discusso l’ultimo scenario: preload dei dati da visualizzare nel tooltip”. Questo è un classico esempio in cui il preload può essere benefico e fornire una esperienza utente più fluida.

Comprendere Il Problema

Permettetemi di definire il problema qui. Immaginate questa situazione:

  1. Hai un componente che mostra informazioni specifiche.

  2. C’è un elemento dentro questo componente che mostra un altro tooltip/popover quando passi sopra con il mouse.

  3. Il tooltip recupera i dati quando viene caricato.

Adesso immaginate che l’utente passi sopra l’elemento e deve attendere che i dati siano recuperati e visualizzati nel tooltip. Durante questo attesa, vede il caricamento dell’animazione.

Il scenario sarà simile a questo:

E’ infastidente quanto il utente debba attendere ogni volta che passa il mouse sopra all’immagine:

Per risolvere questo problema, ci sono due soluzioni che possono aiutarti a iniziare e personalizzare l’ottimizzazione secondo le tue necessità.

Soluzione #1: Prefetching Dati nel Componente Padre

Questa soluzione è ispirata da il blog di Martin Fowler. Permette di recuperare i dati prima che appaia il popup, invece di farlo alla caricatura del componente.

Il popup appare quando passi il mouse sopra. Possiamo recuperare i dati quando l’鼠标 entra nel componente padre. Prima che il componente vero e proprio, l’immagine, sia passato sopra, avremo i dati per il popover e li passaremo al componente del popover.

Questa soluzione non rimuove del tutto lo stato di caricamento, ma aiuta a ridurre significativamente le possibilità di vedere lo stato di caricamento.

Soluzione #2: Prefetch Dati alla Caricatura della Pagina

Questa soluzione è ispirata da x.com in cui, per il componente del popover, recuperano i dati parzialmente alla caricatura della pagina principale e recuperano il resto dei dati quando il componente viene montato.

Come potete vedere dall’video soprastante, i dettagli del profilo utente sono visualizzati nel popover. Se guardate da vicino, i dettagli relativi ai seguaci vengono recuperati in seguito.

Questa tecnica è altamente efficiente quando ci sono molti dati da visualizzare nella popover, ma il recupero di questi dati può essere costoso alla montata della popover o alla caricatura della pagina principale.

Una soluzione migliore sarebbe quella di caricare parzialmente i dati richiesti nella pagina principale e caricare il resto dei dati quando il componente viene montato.

Nel nostro esempio, abbiamo recuperato i dati per la popover quando il cursore è entrato nell’elemento genitore dell’immagine. Ora immaginate che deviate recuperare dettagli aggiuntivi una volta che i dati della popover sono caricati. Quindi sulla base del metodo di x.com illustrato sopra, possiamo recuperare dati aggiuntivi alla caricatura della popover. Ecco il risultato:

In questo caso, facciamo le seguenti operazioni:

  • Recuperiamo i dati principali necessari a renderizzare la popover quando il mouse entra nell’elemento genitore dell’immagine.

  • Ci dà abbastanza tempo per recuperare i dati principali.

  • Alla caricatura della popover, recuperiamo altri dati, ovvero il conteggio degli album. Mentre l’utente legge dati come nome e email, avremo i dati successivi pronti per essere visualizzati.

In questo modo, possiamo apportare piccole e intelligenti modifiche per minimizzare lo spazio vuoto degli indicatori di caricamento sullo schermo 😊.

Come implementare il pre-recupero con React.

In questa sezione passerò in rassegna brevemente il modo di implementare l’app di esempio per il prefetching illustrato sopra.

Configurazione del progetto

Per iniziare a creare l’app di prefetching, segui il processo riportato di seguito:

Puoi utilizzare vitejs (questo è ciò che ho usato) o create-react-app per creare la tua app. Inserisci il comando riportato di seguito nel tuo terminale:

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

Una volta creata l’app, dovresti avere la seguente struttura di cartelle quando aprisci la cartella prefetch-example con VS Code.

Adesso scendiamo nei componenti che stiamo per costruire per questa app.

Componenti

In questo esempio utilizzeremo 3 componenti:

  • PopoverExample

  • UserProfile

  • UserProfileWithFetching

PopoverExample Componente

Cominciamo con il primo componente, che è il PopoverExample. Questo componente mostra un avatar immagine e del testo sul lato destro di esso. Dovrebbe apparire come questo:

Lo scopo di questo componente è servire come esempio simile ai casi reali. L’immagine in questo componente carica un componente popover quando viene passato sopra con il mouse.

Qui c’è il codice per il 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>
    );
}

Ci sono diverse cose che stanno accadendo qui, permettimi di spiegarle passo dopo passo:

  • Abbiamo un div genitore chiamato hover-example che contiene un’immagine e del testo.

  • Poi, abbiamo reso condizionalmente un div con il nome della classe floating. Questo è il componente effettivo del popover che si apre quando passi il mouse sull’immagine.

  • All’interno del popover abbiamo caricato condizionalmente UserProfile e il caricatore dello scheletro. Questo loader appare quando stiamo recuperando i dati per il profilo dell’utente. Di più su questo più tardi.

  • Abbiamo utilizzato la libreria react-content-loader nel componente MyLoader. questa libreria ha anche un sito web che vi aiuta a creare caricatori, puoi controllarlo qui.

Componente UserProfile

Ora che abbiamo definito il nostro esempio Popover, è il momento di entrare nei dettagli del componente UserProfile.

Questo componente appare all’interno del componente popover. L’obiettivo di questo componente è caricare i dettagli name email phone website che vengono recuperati dalla API placeholder JSON.

Per dimostrare l’esempio di prefetching, dobbiamo assicurarci che il componente UserProfile si comporti solo come un componente presentazionale; cioè, non ci sia alcuna logica esplicita di recupero dentro di esso.

La cosa chiave da notare su questo componente è che il recupero dei dati avviene dal componente genitore, che è il componente PopoverExample. In questo componente, iniziamo a recuperare i dati quando il mouse entra in questo componente (l’evento mouseenter). Questa è la soluzione numero 1 che abbiamo discusso precedentemente.

Questo dà abbastanza tempo per il recupero dei dati fino a quando l’utente passa sopra all’immagine. Ecco il codice:

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

Questo componente utilizza la proprietà hasAdditionalDetails. L’obiettivo di questa proprietà è caricare dati aggiuntivi quando il componente viene montato. Illustra la soluzione numero 2 menzionata sopra.

Componente UserProfileWithFetching

Questo componente è piuttosto simile a quello del componente UserProfile. Contiene solo la logica per il recupero dati quando il componente viene caricato. L’obiettivo di questo componente è mostrare come sarebbe la soluzione generica senza la tecnica di prefetching.

Quindi, questo componente caricherà sempre i dati quando il componente viene montato, mostrando il loader scheletrico.

Ecco il codice:

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

Il codice completo di quest’app può essere trovato qui.

Eccessivo prefetching può anche causare lentezza

Un consiglio, il prefetching eccessivo non è buono perché:

  • Potrebbe rallentare la vostra app.

  • Può degradare l’esperienza utente se la prefetch non è applicata strategicamente.

La prefetch deve essere applicata quando si conosce il comportamento dell’utente. Cioè, è possibile predire il movimento dell’utente attraverso i metriche e determinare se visita una pagina spesso. In questo caso, la prefetch è una buona idea.

Quindi ricordate di applicare sempre la prefetch in modo strategico.

Riepilogo

Ecco qui la fine! Spero che vi piaccia il mio post del blog. In questo post del blog, hai imparato che l’implementazione della prefetch può notevolmente migliorare la velocità e la responsività dell’applicazione web, aumentando la soddisfazione utente.

Per ulteriore lettura, consultate gli articoli seguenti:

Per maggiori contenuti, puoi seguirmi su Twitter, GitHub, e LinkedIn.