Cách triển khai cuộn và phân trang vô hạn với truy vấn Next.js và TanStack
Hầu hết các ứng dụng bạn phát triển sẽ quản lý dữ liệu; khi các chương trình tiếp tục mở rộng quy mô, số lượng chương trình có thể ngày càng lớn. Khi các ứng dụng không quản lý được lượng lớn dữ liệu một cách hiệu quả, chúng sẽ hoạt động kém.
Việc sử dụng phân trang và cuộn vô hạn là các phương pháp thiết thực để tối ưu hóa hiệu quả ứng dụng, cho phép cải thiện khả năng xử lý kết xuất dữ liệu đồng thời nâng cao trải nghiệm tổng thể của người dùng.
Phân trang và cuộn vô hạn bằng TanStack Query
TanStack Query €”bản chuyển thể của React Query€”là một thư viện quản lý trạng thái mạnh mẽ dành cho các ứng dụng JavaScript. Nó cung cấp một giải pháp hiệu quả để quản lý trạng thái ứng dụng, cùng với các chức năng khác, bao gồm các tác vụ liên quan đến dữ liệu như bộ nhớ đệm.
Phân trang là phương pháp tổ chức các tập dữ liệu khổng lồ bằng cách chia chúng thành các phần nhỏ hơn, dễ điều hướng thông qua việc sử dụng các điều khiển phân trang. Mặt khác, cuộn vô hạn cung cấp một phương pháp duyệt thích ứng, theo đó khi người dùng cuộn xuống, thông tin bổ sung sẽ được tải và hiển thị liền mạch, do đó loại bỏ yêu cầu điều hướng trực tiếp.
Phân trang và cuộn vô hạn đều là phương pháp quản lý và hiển thị khối lượng thông tin đáng kể theo cách thân thiện với người dùng, mỗi phương pháp đều có ưu điểm và nhược điểm riêng tùy thuộc vào nhu cầu cụ thể của ứng dụng.
Bạn có thể định vị mã nguồn của dự án này trong kho GitHub được chỉ định cho dự án đó.
Thiết lập dự án Next.js
Để bắt đầu quá trình, hãy thiết lập dự án Next.js bằng cách cài đặt phiên bản mới nhất của nó, phiên bản 13, sử dụng thư mục “Ứng dụng” làm nền tảng.
npx create-next-app@latest next-project --app
Để tiếp tục, bạn sẽ cần cài đặt gói TanStack trong dự án của mình bằng cách sử dụng npm, trình quản lý gói được sử dụng rộng rãi cho các ứng dụng Node.js.
npm i @tanstack/react-query
Tích hợp Truy vấn TanStack trong Ứng dụng Next.js
Để kết hợp TanStack Query trong dự án Next.js của bạn, cần thiết lập và khởi tạo một phiên bản mới của TanStack Query ở cốt lõi của ứng dụng-cụ thể là trong tệp bố cục.js. Điều này có thể đạt được bằng cách nhập cả QueryClient và QueryClientProvider từ TanStack Query. Sau đó, bao quanh thuộc tính của trẻ bằng QueryClientProvider, có cấu trúc như dưới đây:
"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 };
Cấu hình này cấp toàn quyền cho TanStack Query để hiểu và thao tác trạng thái hiện tại của phần mềm.
Triển khai phân trang bằng hook useQuery
Việc sử dụng hook useQuery
tạo điều kiện thuận lợi cho việc truy xuất và xử lý dữ liệu hiệu quả bằng cách kết hợp các thuộc tính phân trang, bao gồm số trang, để thu thập liền mạch các phần thông tin được nhắm mục tiêu.
Advanced Repeat rất linh hoạt và cung cấp nhiều lựa chọn để điều chỉnh quy trình truy xuất dữ liệu theo yêu cầu cụ thể của bạn. Điều này bao gồm khả năng thiết lập cài đặt bộ nhớ đệm và quản lý các điều kiện tải một cách dễ dàng, mang lại kết quả phân trang mượt mà và thống nhất.
Để kết hợp phân trang trong ứng dụng Next.js của chúng tôi, chúng tôi sẽ thiết lập tệp Pagination/page.js nằm trong thư mục “src/app” của mã nguồn. Tệp cụ thể này sẽ chứa các câu lệnh nhập cần thiết để triển khai chức năng phân trang.
"use client"
import React, { useState } from 'react';
import { useQuery} from '@tanstack/react-query';
import './page.styles.css';
Sau đó, xác định thành phần chức năng React. Bên trong thành phần này, bạn cần xác định một hàm sẽ tìm nạp dữ liệu từ API bên ngoài. Trong trường hợp này, hãy sử dụng JSONPlaceholder API để tìm nạp một nhóm bài đăng.
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
}
Hook useQuery
có thể được xác định bằng cách chỉ định một đối tượng có một số cặp khóa-giá trị đại diện cho các tham số khác nhau cần thiết để tìm nạp dữ liệu từ API GraphQL. Các tham số này có thể bao gồm chính chuỗi truy vấn, các biến để tùy chỉnh truy vấn, các tùy chọn để kiểm soát cách tìm nạp và cập nhật dữ liệu cũng như thử lại các chính sách trong trường hợp có lỗi mạng hoặc các sự cố khác. Bằng cách cung cấp các tham số này trong ngữ cảnh của hook useQuery
, chúng tôi cho phép cây thành phần React quản lý trạng thái được liên kết với vòng đời truy vấn, cho phép cập nhật hiệu quả hơn và trải nghiệm người dùng mượt mà hơn.
const { isLoading, isError, error, data } = useQuery({
keepPreviousData: true,
queryKey: ['posts', page],
queryFn: fetchPosts,
});
Thuộc tính “keepPreviousData” được đặt thành “true”, do đó cho phép lưu giữ dữ liệu trước đó khi thu thập thông tin mới. Biến “queryKey” bao gồm danh sách các khóa, bao gồm điểm cuối bắt buộc và số trang mong muốn. Cuối cùng, việc gọi hàm “fetchPosts” đóng vai trò kích hoạt việc thu thập dữ liệu.
Trong cuộc thảo luận trước, chúng ta đã đề cập đến thực tế là hook cung cấp nhiều trạng thái có thể được giải cấu trúc theo cách tương tự như việc mổ xẻ các mảng và đối tượng. Bằng cách sử dụng các trạng thái này, có thể nâng cao trải nghiệm người dùng tổng thể bằng cách hiển thị các giao diện phù hợp trong giai đoạn truy xuất dữ liệu. Trong số các tùy chọn có sẵn là’isLoading’,‘isError’và nhiều tùy chọn khác.
Để hiển thị các màn hình thông báo khác nhau tùy thuộc vào trạng thái hiện tại của quy trình đang diễn ra, cần kết hợp đoạn mã được cung cấp để cho phép hiển thị các màn hình thông báo đặc biệt cho từng giai đoạn của quy trình đang diễn ra.
if (isLoading) {
return (<h2>Loading...</h2>);
}
if (isError) {
return (<h2 className="error-message">{error.message}</h2>);
}
Tóm lại, điều cần thiết là cung cấp mã cho các thành phần JavaScript sẽ được hiển thị trong trình duyệt web. Điều này không chỉ đảm bảo hiển thị chính xác các thành phần mà còn cho phép giao tiếp giữa các ứng dụng phía máy khách và phía máy chủ thông qua việc sử dụng các trình xử lý sự kiện và phương pháp tìm nạp dữ liệu.
Bằng cách sử dụng chức năng do hook useQuery cung cấp, một tập hợp các bài đăng được truy xuất sẽ được tích lũy một cách hiệu quả trong giới hạn của biến dữ liệu. Việc tổng hợp này nhằm tạo điều kiện thuận lợi cho việc duy trì trạng thái nội bộ của ứng dụng. Sau đó, quá trình truyền tải có thể được thực hiện dựa trên việc tích lũy các bài đăng được lưu trữ trong biến nói trên, đỉnh điểm là hiển thị trực quan của chúng trên giao diện duyệt web.
Để cung cấp cho người dùng khả năng điều hướng qua dữ liệu được phân trang bổ sung, cần kết hợp hai nút điều hướng, được gắn nhãn “Trước” và “Tiếp theo”. Các nút này sẽ cho phép người dùng truy cập các trang nội dung khác khi chúng được trình bày ở định dạng phân trang.
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>
);
Cuối cùng, khởi động máy chủ phát triển.
npm run dev
Vui lòng điều hướng đến “ http://localhost:3000/Pagination ” trên trình duyệt web của bạn để được hướng dẫn thêm.
Việc kết hợp thành phần Phân trang trong cấu trúc thư mục của ứng dụng sẽ nhắc Next.js nhận dạng nó như một tuyến được chỉ định, cho phép điều hướng liền mạch đến trang web tương ứng thông qua URL cụ thể của nó.
Cuộn vô hạn bằng cách sử dụng hook useInfiniteQuery
Cuộn vô hạn tạo ra giao diện người dùng dường như không bị gián đoạn bằng cách tải động nội dung bổ sung khi người dùng tiếp tục cuộn. Bạn có thể tìm thấy một ví dụ điển hình về điều này trong chức năng của YouTube, trong đó các mục video mới được truy xuất và trình bày một cách dễ dàng mà không có bất kỳ sự gián đoạn nào có thể quan sát được khi người dùng duyệt qua trang web.
Việc sử dụng hook useInfiniteQuery
cho phép người dùng tạo điều kiện thuận lợi cho việc cuộn vô hạn để truy xuất dữ liệu từ máy chủ từ xa, trong đó các trang tiếp theo được tự động yêu cầu và hiển thị khi người dùng dần dần đi xuống.
Để kết hợp chức năng cuộn vô hạn trong ứng dụng của bạn, cần phải tạo một tệp JavaScript mới có tên “InfiniteScroll/page.js” và đặt nó trong thư mục “src/app”. Sau đó, bạn sẽ cần nhập nhiều mô-đun khác nhau, bao gồm các thành phần React và thư viện quản lý trạng thái, giúp tạo điều kiện tích hợp liền mạch tính năng này với cơ sở mã hiện có của bạn.
"use client"
import React, { useRef, useEffect, useState } from 'react';
import { useInfiniteQuery } from '@tanstack/react-query';
import './page.styles.css';
Tiếp theo, chúng ta sẽ phát triển một thành phần chức năng React và trong đó, thiết lập một hàm truy xuất nội dung của các bài đăng theo cách tương tự như phương thức được sử dụng để phân trang.
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
}
Ngược lại với việc triển khai phân trang, không gây ra bất kỳ sự chậm trễ nào trong việc tìm nạp dữ liệu, mã cụ thể này bao gồm việc tạm dừng có chủ ý khoảng hai giây trước khi có được thông tin mới. Sự chậm trễ có mục đích này được thiết kế để cho phép người dùng có nhiều thời gian để cuộn và khám phá tập dữ liệu hiện tại, cuối cùng dẫn đến việc tự động truy xuất bộ dữ liệu cập nhật khi họ tiếp tục trải nghiệm duyệt web.
Sau khi khởi tạo, hook tùy chỉnh useInfiniteQuery
sẽ thiết lập kết nối với máy chủ, truy xuất bộ dữ liệu ban đầu khi kết xuất thành phần. Sau đó, khi người dùng cuộn qua nội dung, hook sẽ tự động thu thập các lô thông tin tiếp theo và kết hợp chúng trong thành phần mà không cần thêm bất kỳ thông tin đầu vào nào.
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) : [];
Biến posts
đóng vai trò như một bộ tích lũy để thu thập tất cả các bài đăng từ các trang khác nhau và sắp xếp chúng thành một mảng thống nhất. Bằng cách đó, nó đơn giản hóa quá trình lặp lại và hiển thị từng bài đăng riêng lẻ.
Người ta có thể triển khai một chức năng sử dụng API Intersection Observer để xác định khi nào các phần tử cụ thể nằm trong giới hạn màn hình hiển thị của người dùng bằng cách xác định một hàm. Điều này cho phép giám sát hành vi cuộn của người dùng và tự động tải dữ liệu bổ sung khi họ ở gần cuối danh sách.
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]);
Tóm lại, hãy kết hợp các thành phần JSX sẽ được hiển thị trong trình duyệt web.
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>
);
Sau khi hoàn tất các sửa đổi của bạn, vui lòng điều hướng đến “ http://localhost:3000/InfiniteScroll ” để trực tiếp quan sát chức năng của chúng.
Truy vấn TanStack: Không chỉ là tìm nạp dữ liệu
TanStack Query thể hiện tính linh hoạt của nó như một thư viện quản lý dữ liệu toàn diện thông qua việc triển khai các tính năng phân trang và cuộn vô hạn. Những chức năng này thể hiện nhiều khả năng được cung cấp bởi công cụ mạnh mẽ này.
Thông qua một loạt chức năng phong phú, giải pháp này tối ưu hóa việc quản lý dữ liệu ứng dụng, bao gồm việc kiểm soát trạng thái một cách hiệu quả. Kết hợp với nhiều hoạt động tập trung vào dữ liệu khác, nó nâng cao hiệu quả hoạt động và sự hài lòng của người dùng đối với các ứng dụng dựa trên web.