Jak zaimplementować nieskończone przewijanie i paginację za pomocą Next.js i TanStack Query
Większość tworzonych aplikacji będzie zarządzać danymi; w miarę skalowania programów może być ich coraz więcej. Gdy aplikacje nie potrafią skutecznie zarządzać dużymi ilościami danych, osiągają słabe wyniki.
Wykorzystanie paginacji i nieskończonego przewijania to praktyczne metody optymalizacji wydajności aplikacji, pozwalające na lepszą obsługę renderowania danych, jednocześnie poprawiając ogólne wrażenia użytkownika.
Paginacja i nieskończone przewijanie przy użyciu TanStack Query
TanStack Query - adaptacja React Query - to solidna biblioteka zarządzania stanem dla aplikacji JavaScript. Oferuje wydajne rozwiązanie do zarządzania stanem aplikacji, wśród innych funkcji, w tym zadań związanych z danymi, takich jak buforowanie.
Paginacja to metoda organizowania dużych zbiorów danych poprzez dzielenie ich na mniejsze, łatwe w nawigacji sekcje za pomocą kontrolek paginacji. Z drugiej strony, nieskończone przewijanie oferuje adaptacyjne podejście do przeglądania, w którym gdy użytkownik przewija w dół, dodatkowe informacje są ładowane i wyświetlane płynnie, eliminując w ten sposób wymóg bezpośredniej nawigacji.
Paginacja i nieskończone przewijanie to metody zarządzania i wyświetlania znacznych ilości informacji w przyjazny dla użytkownika sposób, z których każda ma swoje zalety i wady w zależności od konkretnych potrzeb aplikacji.
Kod źródłowy tego projektu można znaleźć w przeznaczonym dla niego repozytorium GitHub.
Konfigurowanie projektu Next.js
Aby rozpocząć proces, utwórz projekt Next.js, instalując jego najnowszą iterację, wersję 13, która wykorzystuje katalog “App” jako podstawę.
npx create-next-app@latest next-project --app
Aby kontynuować, należy zainstalować pakiet TanStack w projekcie przy użyciu npm, powszechnie używanego menedżera pakietów dla aplikacji Node.js.
npm i @tanstack/react-query
Integracja TanStack Query w aplikacji Next.js
Aby włączyć TanStack Query do projektu Next.js, konieczne jest ustanowienie i zainicjowanie nowej instancji TanStack Query w rdzeniu aplikacji - w szczególności w pliku layout.js. Można to osiągnąć, importując zarówno QueryClient, jak i QueryClientProvider z TanStack Query. Następnie należy otoczyć właściwość dziecka za pomocą QueryClientProvider o strukturze przedstawionej poniżej:
"use client"
import React from 'react'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const metadata = {
title: 'Create Next App',
description: 'Generated by create next app',
};
export default function RootLayout({ children }) {
const queryClient = new QueryClient();
return (
<html lang="en">
<body>
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
</body>
</html>
);
}
export { metadata };
Ta konfiguracja daje pełne uprawnienia dla TanStack Query do zrozumienia i manipulowania bieżącym stanem oprogramowania.
Implementacja paginacji za pomocą haka useQuery
Wykorzystanie haka useQuery
ułatwia wydajne pobieranie i obsługę danych poprzez włączenie atrybutów paginacji, w tym numerów stron, w celu płynnego pozyskiwania docelowych porcji informacji.
Advanced Repeat jest bardzo wszechstronny i oferuje szereg możliwości dostosowania procesu pobierania danych do konkretnych wymagań. Obejmuje to możliwość ustalenia ustawień buforowania i łatwego zarządzania warunkami ładowania, co skutkuje płynnym i ujednoliconym wynikiem paginacji.
Aby włączyć paginację do naszej aplikacji Next.js, utworzymy plik Pagination/page.js znajdujący się w katalogu “src/app” kodu źródłowego. Ten konkretny plik będzie zawierał instrukcje importu niezbędne do wdrożenia funkcji paginacji.
"use client"
import React, { useState } from 'react';
import { useQuery} from '@tanstack/react-query';
import './page.styles.css';
Następnie zdefiniuj komponent funkcjonalny React. Wewnątrz tego komponentu należy zdefiniować funkcję, która będzie pobierać dane z zewnętrznego interfejsu API. W tym przypadku użyj interfejsu API JSONPlaceholder , aby pobrać zestaw postów.
export default function Pagination() {
const [page, setPage] = useState(1);
const fetchPosts = async () => {
try {
const response = await fetch(`https://jsonplaceholder.typicode.com/posts?
_page=${page}&_limit=10`);
if (!response.ok) {
throw new Error('Failed to fetch posts');
}
const data = await response.json();
return data;
} catch (error) {
console.error(error);
throw error;
}
};
// add the following code here
}
Hak useQuery
można zdefiniować, określając obiekt z kilkoma parami klucz-wartość, które reprezentują różne parametry wymagane do pobierania danych z interfejsu API GraphQL. Parametry te mogą obejmować sam ciąg zapytania, zmienne do dostosowywania zapytania, opcje kontrolowania sposobu pobierania i aktualizacji danych oraz zasady ponawiania prób w przypadku błędów sieciowych lub innych problemów. Dostarczając te parametry w kontekście haka useQuery
, umożliwiamy drzewu komponentów React zarządzanie stanem związanym z cyklem życia zapytania, co pozwala na bardziej wydajne aktualizacje i płynniejsze doświadczenia użytkownika.
const { isLoading, isError, error, data } = useQuery({
keepPreviousData: true,
queryKey: ['posts', page],
queryFn: fetchPosts,
});
Właściwość “keepPreviousData” jest ustawiona na “true”, umożliwiając tym samym zachowanie wcześniejszych danych podczas pozyskiwania nowych informacji. Zmienna “queryKey” zawiera listę kluczy, w tym wymagany punkt końcowy i żądany numer strony. Wreszcie, wywołanie funkcji “fetchPosts” służy jako wyzwalacz do pozyskiwania danych.
W poprzedniej dyskusji poruszyliśmy fakt, że hak oferuje wiele stanów, które można dekonstruować w sposób analogiczny do rozbioru tablic i obiektów. Wykorzystując te stany, możliwe staje się zwiększenie ogólnego doświadczenia użytkownika poprzez renderowanie odpowiednich interfejsów podczas fazy pobierania danych. Wśród szeregu dostępnych opcji znajdują się “isLoading”, “isError” i różne inne.
Aby wyświetlać różne ekrany komunikatów w zależności od aktualnego stanu postępującej procedury, konieczne jest włączenie dostarczonego fragmentu kodu, który umożliwia wyświetlanie różnych komunikatów dla każdego etapu trwającego procesu.
if (isLoading) {
return (<h2>Loading...</h2>);
}
if (isError) {
return (<h2 className="error-message">{error.message}</h2>);
}
Podsumowując, niezbędne jest dostarczenie kodu dla komponentów JavaScript, które będą wyświetlane w przeglądarce internetowej. Nie tylko zapewnia to prawidłowe renderowanie komponentów, ale także umożliwia komunikację między aplikacjami po stronie klienta i serwera za pomocą obsługi zdarzeń i metod pobierania danych.
Wykorzystując funkcjonalność haka useQuery, kolekcja pobranych postów jest skutecznie gromadzona w ramach zmiennej danych. Ta agregacja służy ułatwieniu utrzymania wewnętrznego stanu aplikacji. Następnie można przeprowadzić przeszukiwanie po nagromadzeniu postów przechowywanych we wspomnianej zmiennej, co kończy się ich wizualnym wyświetleniem w interfejsie przeglądania.
Aby zapewnić użytkownikom możliwość poruszania się po dodatkowych danych stronicowanych, konieczne jest włączenie dwóch przycisków nawigacyjnych, oznaczonych jako “Poprzedni” i “Następny”. Przyciski te umożliwią użytkownikom dostęp do kolejnych stron treści, gdy są one prezentowane w formacie podzielonym na strony.
return (
<div>
<h2 className="header">Next.js Pagination</h2>
{data && (
<div className="card">
<ul className="post-list">
{data.map((post) => (
<li key={post.id} className="post-item">{post.title}</li>
))}
</ul>
</div>
)}
<div className='btn-container'>
<button
onClick={() => setPage(prevState => Math.max(prevState - 1, 0))}
disabled={page === 1}
className="prev-button"
>Prev Page</button>
<button
onClick={() => setPage(prevState => prevState \\+ 1)}
className="next-button"
>Next Page</button>
</div>
</div>
);
Na koniec należy uruchomić serwer deweloperski.
npm run dev
Przejdź do " http://localhost:3000/Pagination " w przeglądarce internetowej, aby uzyskać dalsze instrukcje.
Włączenie komponentu Pagination do struktury katalogów aplikacji powoduje, że Next.js rozpoznaje go jako wyznaczoną trasę, umożliwiając płynną nawigację do odpowiedniej strony internetowej za pośrednictwem określonego adresu URL.
Nieskończone przewijanie przy użyciu haka useInfiniteQuery
Nieskończone przewijanie tworzy pozornie nieprzerwany interfejs użytkownika poprzez dynamiczne ładowanie dodatkowej zawartości, gdy użytkownik kontynuuje przewijanie. Przykład tego można znaleźć w funkcjonalności YouTube, gdzie nowe wpisy wideo są bez wysiłku pobierane i prezentowane bez żadnych zauważalnych przerw, gdy użytkownik schodzi przez stronę internetową.
Wykorzystanie haka useInfiniteQuery
umożliwia ułatwienie nieskończonego przewijania poprzez pobieranie danych ze zdalnego serwera, w którym kolejne strony są automatycznie żądane i renderowane, gdy użytkownik stopniowo schodzi.
Aby włączyć funkcję nieskończonego przewijania do swojej aplikacji, konieczne jest utworzenie nowego pliku JavaScript o nazwie “InfiniteScroll/page.js” i umieszczenie go w katalogu “src/app”. Następnie należy zaimportować różne moduły, w tym komponenty React i biblioteki zarządzania stanem, które ułatwiają płynną integrację tej funkcji z istniejącą bazą kodu.
"use client"
import React, { useRef, useEffect, useState } from 'react';
import { useInfiniteQuery } from '@tanstack/react-query';
import './page.styles.css';
Następnie opracujemy komponent funkcjonalny React i utworzymy w nim funkcję, która pobiera zawartość postów w sposób analogiczny do metody wykorzystywanej do paginacji.
export default function InfiniteScroll() {
const listRef = useRef(null);
const [isLoadingMore, setIsLoadingMore] = useState(false);
const fetchPosts = async ({ pageParam = 1 }) => {
try {
const response = await fetch(`https://jsonplaceholder.typicode.com/posts?
_page=${pageParam}&_limit=5`);
if (!response.ok) {
throw new Error('Failed to fetch posts');
}
const data = await response.json();
await new Promise((resolve) => setTimeout(resolve, 2000));
return data;
} catch (error) {
console.error(error);
throw error;
}
};
// add the following code here
}
W przeciwieństwie do implementacji paginacji, która nie wprowadza żadnych opóźnień w pobieraniu danych, ten konkretny kod zawiera celową pauzę trwającą około dwóch sekund przed pobraniem nowych informacji. To celowe opóźnienie ma na celu zapewnienie użytkownikom wystarczającej ilości czasu na przewijanie i eksplorowanie bieżącego zbioru danych, co ostatecznie skutkuje automatycznym pobieraniem zaktualizowanego zestawu danych w miarę kontynuowania przeglądania.
Po inicjalizacji, niestandardowy hak useInfiniteQuery
nawiązuje połączenie z serwerem, pobierając początkowy zestaw danych podczas renderowania komponentu. Następnie, gdy użytkownik przewija zawartość, hak autonomicznie pozyskuje kolejne partie informacji i włącza je do komponentu bez konieczności dalszego wprowadzania danych.
const { data, fetchNextPage, hasNextPage, isFetching } = useInfiniteQuery({
queryKey: ['posts'],
queryFn: fetchPosts,
getNextPageParam: (lastPage, allPages) => {
if (lastPage.length < 5) {
return undefined;
}
return allPages.length \\+ 1;
},
});
const posts = data ? data.pages.flatMap((page) => page) : [];
Zmienna posts
służy jako akumulator do zbierania wszystkich postów z różnych stron i spłaszcza je w ujednoliconą tablicę. Upraszcza to proces iteracji i renderowania każdego posta z osobna.
Można zaimplementować funkcjonalność, która wykorzystuje Intersection Observer API, aby ustalić, kiedy określone elementy znajdą się w granicach widocznego ekranu użytkownika, definiując funkcję. Pozwala to na monitorowanie zachowania użytkownika podczas przewijania i automatyczne ładowanie dodatkowych danych, gdy zbliża się on do końca listy.
const handleIntersection = (entries) => {
if (entries[0].isIntersecting && hasNextPage && !isFetching && !isLoadingMore) {
setIsLoadingMore(true);
fetchNextPage();
}
};
useEffect(() => {
const observer = new IntersectionObserver(handleIntersection, { threshold: 0.1 });
if (listRef.current) {
observer.observe(listRef.current);
}
return () => {
if (listRef.current) {
observer.unobserve(listRef.current);
}
};
}, [listRef, handleIntersection]);
useEffect(() => {
if (!isFetching) {
setIsLoadingMore(false);
}
}, [isFetching]);
Podsumowując, włącz komponenty JSX, które będą wyświetlane w przeglądarce internetowej.
return (
<div>
<h2 className="header">Infinite Scroll</h2>
<ul ref={listRef} className="post-list">
{posts.map((post) => (
<li key={post.id} className="post-item">
{post.title}
</li>
))}
</ul>
<div className="loading-indicator">
{isFetching ? 'Fetching...' : isLoadingMore ? 'Loading more...' : null}
</div>
</div>
);
Po zakończeniu modyfikacji, przejdź do " http://localhost:3000/InfiniteScroll “, aby zobaczyć ich funkcjonalność z pierwszej ręki.
TanStack Query: Więcej niż tylko pobieranie danych
TanStack Query pokazuje swoją wszechstronność jako kompleksowa biblioteka do zarządzania danymi poprzez implementację funkcji paginacji i nieskończonego przewijania. Funkcje te demonstrują szeroki zakres możliwości oferowanych przez to potężne narzędzie.
Dzięki szerokiej gamie funkcji, rozwiązanie to optymalizuje zarządzanie danymi aplikacji, obejmując skuteczną kontrolę nad stanem. W połączeniu z różnymi innymi operacjami skoncentrowanymi na danych, zwiększa wydajność operacyjną i satysfakcję użytkowników aplikacji internetowych.