Indlæs API-indhold, efterhånden som elementer bliver synlige for brugere i React

TL;DR; Du kan reducere antallet af forespørgsler, du foretager på fortegnelsessider, ved at indlæse indhold, efterhånden som det bliver vist. Brug react-intersection-observer til at registrere, hvornår et element bliver synligt, og react-content-loader til at vise en indholdsrig pladsholderindlæser.

Du har udviklet din backend API og bygget din React SPA, men du finder ud af, at nogle sider faktisk laver mange anmodninger (enten store eller mange) til din backend og er blevet lidt træge. Gode ​​nyheder alle sammen, det kan I forbedre.

Lad os overveje et typisk layout for en "listeside". Det, der betyder noget her, er ikke designet, men sidens arkitektur. Du har en liste over elementer og derefter for hvert element en liste over børn. Alle disse skal indlæses dynamisk fra din API.

Hvad er de traditionelle tilgange?

  1. Én stor forespørgsel: Du laver en fed API-forespørgsel til din backend for at indlæse elementerne og deres børn (f.eks. kommentarer). Det virker, er perfekt gyldigt for et lille antal elementer, men er kun skalerbart, hvis du forventer et højt antal børn. Det største problem er at have kontrol over pagineringen for børnene, hvilket kun er muligt, når du inkluderer børn i forældreforespørgslen. Selv med GraphQL, som giver dig mulighed for at paginere børn, ville du gentage den overordnede forespørgsel for hver ny side - dette er ikke optimalt.
  2. Mange små forespørgsler: Du indlæser varer - eller vare-id'er - ved hjælp af en listeforespørgsel, og for hver vare indlæser du vareteksten (hvis den ikke er indlæst som en del af den første forespørgsel) og børnene. Denne tilgang er skalerbar og giver dig bedre kontrol over paginering.

Mulighed 2 er mere flydende med hensyn til arkitektur. Problemet er, at du ender med at lave en hel del forespørgsler ved sideindlæsning afhængigt af antallet af elementer, du ønsker at vise ved den første sideindlæsning. Indlæsning af 100 elementer betyder, at du ender med at lave N x 100 forespørgsler på din API, hvor N er antallet af korrelerede ressourcer, du skal indlæse for hvert element.

I betragtning af at de fleste af elementerne vil være under skillelinjen, virker det som et enormt spild af ressourcer at indlæse alt ved sideindlæsning. En bedre tilgang ville være at indlæse elementer, efterhånden som de bliver synlige for brugerne.

Lad os gøre netop det.

UseInView-krogen til undsætning

React-intersection-observator-biblioteket giver en hook, der registrerer, når et element bliver synligt for brugerne.

Den har masser af konfigurationsmuligheder - såsom at konfigurere en procentdel af objekthøjden for, hvornår inView-hændelsen skal udløses - men vi vil gå med den mest grundlæggende implementering her.

Tilføj react-intersection-observator til dit projekt.

yarn add react-intersection-observer

Nu kan du bruge inView-krogen på dine sideelementer til betinget at indlæse relaterede børn.

import React, { FunctionComponent } from 'react';
import { useInView } from 'react-intersection-observer';

interface Props {
    item: MyItem;
}

const CommentFeed: FunctionComponent<Props> = ({ item }: Props) => {
  // Inject inView in your component. We specify triggerOnce to avoid flicking.
  const { ref, inView } = useInView({ triggerOnce: true });

  // Load your comments conditionally (hook similar to apollo-graphql in this case)
  // It is important to ensure your API query hook supports some sort of skip
  // option.
  const { loading, data } = useItemComments({ skip: !inView, parentId: item.id });

  // Render your component
  // We must attach the inView ref on a wrapping div so that useInView can detect
  // when this component actually becomes visible to the user
  return (
    <div ref={ref}>
      {data.map(e => <div>{e.body}</div>)}
    </div>
  );
};

export default CommentFeed;

Det er det. Du har lige gemt din backend hundredvis af forespørgsler potentielt.

UseInView-hooken garanterer, at dine API-forespørgsler kun udføres, hvis dit element rent faktisk bliver synligt for brugeren.

Nu har vi øget antallet af komponenter betydeligt, der vil være i en indlæsningstilstand, når brugeren ruller din side. Lad os derfor gøre indlæsningstilstanden pæn og tilfreds.

Indlæser med pladsholder

React-content-loader loader-biblioteket giver dig mulighed for at definere pulserende SVG-objekter, der skal bruges som pladsholder, mens dit indhold indlæses.

Jeg finder denne tilgang bedre end de traditionelle spinners, da den giver dine brugere en indikation af, hvad de kan forvente med hensyn til layout, når dit indhold er indlæst.

Her er et eksempel på en kommentarpladsholderindlæser:

Det bedste ved dette bibliotek er, at forfatteren faktisk udviklede et websted til at hjælpe dig med at designe disse SVG-indlæsere. Bare gå til https://skeletonreact.com og få lyst!

Lad os nu inkorporere den pladsholder i vores komponent.

Installer først biblioteket i dit projekt:

yarn add react-content-loader

Design derefter din komponent på https://skeletonreact.com og føj den til dit projekt:

import React, { FunctionComponent } from 'react';
import ContentLoader from 'react-content-loader';

const CommentFeedLoader: FunctionComponent = (props) => (
  <ContentLoader 
    speed={2}
    width={600}
    height={150}
    viewBox="0 0 600 150"
    backgroundColor="#f5f5f5"
    foregroundColor="#ededed"
    {...props}
  >
    <rect x="115" y="10" rx="3" ry="3" width="305" height="13" /> 
    <rect x="9" y="31" rx="3" ry="3" width="66" height="8" /> 
    <rect x="115" y="34" rx="3" ry="3" width="230" height="5" /> 
    <rect x="115" y="46" rx="3" ry="3" width="160" height="5" /> 
    <rect x="115" y="58" rx="3" ry="3" width="122" height="5" /> 
    <rect x="89" y="0" rx="0" ry="0" width="1" height="73" />
  </ContentLoader>
)

export default CommentFeedLoader;

Vis endelig betinget din loader i din kommentarfeedkomponent:

import React, { FunctionComponent } from 'react';
import { useInView } from 'react-intersection-observer';
import { CommentFeedLoader } from './CommentFeedLoader';

interface Props {
    item: MyItem;
}

const CommentFeed: FunctionComponent<Props> = ({ item }: Props) => {
  // Inject inView in your component. We specify triggerOnce to avoid flicking.
  const { ref, inView } = useInView({ triggerOnce: true });

  // Load your comments conditionally (hook similar to apollo-graphql in this case)
  // It is important to ensure your API query hook supports some sort of skip
  // option.
  const { loading, data } = useItemComments({ skip: !inView, parentId: item.id });

  // Return placeholder if content is loading or has not been viewed yet
  if (loading || !inView) {
    return <CommentFeedLoader />
  }

  // Render your component
  // We must attach the inView ref on a wrapping div so that useInView can detect
  // when this component actually becomes visible to the user
  return (
    <div ref={ref}>
      {data.map(e => <div>{e.body}</div>)}
    </div>
  );
};

export default CommentFeed;

Det var det!

Dine kommentarfeeds indlæses nu dynamisk, mens brugerne ruller på din side, og en god pladsholder-indlæser fortæller dem, at den kommer!