Contents

วิธีการใช้การเลื่อนและการแบ่งหน้าแบบไม่สิ้นสุดด้วย Next.js และ TanStack Query

แอพส่วนใหญ่ที่คุณจะพัฒนาจะจัดการข้อมูล เมื่อโปรแกรมต่างๆ ขยายตัวอย่างต่อเนื่อง ก็อาจมีโปรแกรมจำนวนมากขึ้นเรื่อยๆ เมื่อแอปพลิเคชันล้มเหลวในการจัดการข้อมูลจำนวนมากอย่างมีประสิทธิภาพ แอปพลิเคชันเหล่านั้นก็จะทำงานได้ไม่ดี

การใช้การแบ่งหน้าและการเลื่อนแบบไม่มีที่สิ้นสุดเป็นวิธีการปฏิบัติจริงในการเพิ่มประสิทธิภาพแอปพลิเคชัน ช่วยให้การจัดการการแสดงผลข้อมูลดีขึ้นในขณะเดียวกันก็ปรับปรุงประสบการณ์ผู้ใช้โดยรวม

การแบ่งหน้าและการเลื่อนแบบไม่มีที่สิ้นสุดโดยใช้ TanStack Query

TanStack Query €”การดัดแปลง React Query€” เป็นไลบรารีการจัดการสถานะที่มีประสิทธิภาพสำหรับแอปพลิเคชัน JavaScript โดยนำเสนอโซลูชันที่มีประสิทธิภาพสำหรับการจัดการสถานะแอปพลิเคชัน ท่ามกลางฟังก์ชันอื่นๆ รวมถึงงานที่เกี่ยวข้องกับข้อมูล เช่น การแคช

/th/images/laptop-on-a-desk.jpg

การแบ่งหน้าเป็นวิธีการจัดระเบียบชุดข้อมูลขนาดใหญ่โดยแบ่งออกเป็นส่วนที่เล็กลงและนำทางได้อย่างง่ายดายผ่านการใช้ตัวควบคุมการแบ่งหน้า ในทางกลับกัน การเลื่อนแบบไม่มีที่สิ้นสุดเสนอวิธีการเรียกดูแบบปรับเปลี่ยนได้ โดยที่เมื่อผู้ใช้เลื่อนลง ข้อมูลเพิ่มเติมจะถูกโหลดและแสดงอย่างราบรื่น ดังนั้นจึงไม่จำเป็นต้องมีการนำทางโดยตรง

การแบ่งหน้าและการเลื่อนแบบไม่สิ้นสุดเป็นทั้งวิธีการจัดการและแสดงข้อมูลจำนวนมากในลักษณะที่เป็นมิตรต่อผู้ใช้ โดยแต่ละวิธีมีข้อดีและข้อเสียแตกต่างกันไป ขึ้นอยู่กับความต้องการเฉพาะของแอปพลิเคชัน

คุณสามารถค้นหาซอร์สโค้ดสำหรับโปรเจ็กต์นี้ได้ภายในพื้นที่เก็บข้อมูล GitHub ที่กำหนดไว้

การตั้งค่าโครงการ Next.js

ในการเริ่มต้นกระบวนการ ให้สร้างโปรเจ็กต์ Next.js โดยการติดตั้งเวอร์ชันล่าสุด เวอร์ชัน 13 ซึ่งใช้ไดเรกทอรี “App” เป็นรากฐาน

 npx create-next-app@latest next-project --app 

ในการดำเนินการต่อ คุณจะต้องติดตั้งแพ็คเกจ TanStack ภายในโปรเจ็กต์ของคุณโดยใช้ npm ซึ่งเป็นตัวจัดการแพ็คเกจที่ใช้กันอย่างแพร่หลายสำหรับแอปพลิเคชัน Node.js

 npm i @tanstack/react-query 

รวม TanStack Query ในแอปพลิเคชัน Next.js

หากต้องการรวม TanStack Query เข้ากับโปรเจ็กต์ Next.js ของคุณ จำเป็นต้องสร้างและเริ่มต้นอินสแตนซ์ใหม่ของ TanStack Query ที่แกนหลักของแอปพลิเคชัน โดยเฉพาะภายในไฟล์layout.js สามารถทำได้โดยการนำเข้าทั้ง QueryClient และ QueryClientProvider จาก TanStack Query จากนั้น ให้ล้อมคุณสมบัติของบุตรหลานด้วย QueryClientProvider ซึ่งมีโครงสร้างดังแสดงด้านล่าง:

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

การกำหนดค่านี้ให้สิทธิ์เต็มรูปแบบแก่ TanStack Query ในการทำความเข้าใจและจัดการสถานะปัจจุบันของซอฟต์แวร์

ใช้การแบ่งหน้าโดยใช้ useQuery Hook

การใช้ hook useQuery ช่วยอำนวยความสะดวกในการเรียกค้นและการจัดการข้อมูลอย่างมีประสิทธิภาพ โดยการรวมแอตทริบิวต์การแบ่งหน้า รวมถึงหมายเลขหน้า เพื่อให้ได้มาซึ่งส่วนที่เป็นเป้าหมายของข้อมูลได้อย่างราบรื่น

การทำซ้ำขั้นสูงมีความหลากหลายสูงและมีตัวเลือกมากมายสำหรับปรับแต่งกระบวนการดึงข้อมูลของคุณตามความต้องการเฉพาะของคุณ ซึ่งรวมถึงความสามารถในการสร้างการตั้งค่าแคชและจัดการเงื่อนไขการโหลดได้อย่างง่ายดาย ส่งผลให้ผลลัพธ์การแบ่งหน้าราบรื่นและเป็นหนึ่งเดียว

เพื่อรวมการแบ่งหน้าไว้ในแอปพลิเคชัน Next.js ของเรา เราจะสร้างไฟล์ Pagination/page.js ที่อยู่ในไดเรกทอรี “src/app” ของซอร์สโค้ด ไฟล์นี้โดยเฉพาะจะมีคำสั่งการนำเข้าที่จำเป็นสำหรับการใช้ฟังก์ชันการแบ่งหน้า

 "use client"
import React, { useState } from 'react';
import { useQuery} from '@tanstack/react-query';
import './page.styles.css'; 

จากนั้น กำหนดองค์ประกอบการทำงานของ React ภายในส่วนประกอบนี้ คุณต้องกำหนดฟังก์ชันที่จะดึงข้อมูลจาก API ภายนอก ในกรณีนี้ ให้ใช้ JSONPlaceholder API เพื่อดึงชุดของโพสต์

 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 ได้โดยการระบุออบเจ็กต์ที่มีคู่คีย์-ค่าหลายคู่ที่แสดงถึงพารามิเตอร์ต่างๆ ที่จำเป็นในการดึงข้อมูลจาก GraphQL API พารามิเตอร์เหล่านี้อาจรวมถึงสตริงการสืบค้น ตัวแปรสำหรับการปรับแต่งการสืบค้น ตัวเลือกสำหรับควบคุมวิธีการดึงข้อมูลและอัปเดต และลองนโยบายอีกครั้งในกรณีที่เครือข่ายเกิดข้อผิดพลาดหรือปัญหาอื่น ๆ ด้วยการจัดเตรียมพารามิเตอร์เหล่านี้ภายในบริบทของฮุก useQuery เราเปิดใช้งานแผนผังส่วนประกอบ React เพื่อจัดการสถานะที่เกี่ยวข้องกับวงจรการสืบค้น ช่วยให้อัปเดตมีประสิทธิภาพมากขึ้นและประสบการณ์ผู้ใช้ที่ราบรื่นยิ่งขึ้น

   const { isLoading, isError, error, data } = useQuery({
    keepPreviousData: true,
    queryKey: ['posts', page],
    queryFn: fetchPosts,
  }); 

คุณสมบัติ “keepPreviousData” ถูกตั้งค่าเป็น “true” ซึ่งช่วยให้สามารถเก็บรักษาข้อมูลก่อนหน้าเมื่อได้รับข้อมูลใหม่ ตัวแปร “queryKey” ประกอบด้วยรายการคีย์ รวมถึงจุดสิ้นสุดที่จำเป็นและหมายเลขหน้าที่ต้องการ สุดท้าย การเรียกใช้ฟังก์ชัน"fetchPosts"จะทำหน้าที่เป็นตัวกระตุ้นในการรับข้อมูล

ในการสนทนาครั้งก่อน เราได้กล่าวถึงข้อเท็จจริงที่ว่า hook มีหลายสถานะซึ่งสามารถแยกส่วนได้ในลักษณะที่คล้ายคลึงกับการแยกอาร์เรย์และอ็อบเจ็กต์ ด้วยการใช้สถานะเหล่านี้ จะเป็นไปได้ที่จะปรับปรุงประสบการณ์ผู้ใช้โดยรวมด้วยการแสดงอินเทอร์เฟซที่เหมาะสมในระหว่างขั้นตอนการดึงข้อมูล ในบรรดาตัวเลือกที่มีอยู่ ได้แก่’isLoading’,‘isError’และอื่น ๆ อีกมากมาย

เพื่อที่จะแสดงหน้าจอข้อความต่างๆ ขึ้นอยู่กับสถานะปัจจุบันของขั้นตอนที่กำลังดำเนินอยู่ จำเป็นต้องรวมข้อมูลโค้ดที่ให้มา ซึ่งช่วยให้สามารถแสดงข้อความที่แตกต่างกันสำหรับแต่ละขั้นตอนของกระบวนการที่กำลังดำเนินอยู่

   if (isLoading) {
    return (<h2>Loading...</h2>);
  }

  if (isError) {
    return (<h2 className="error-message">{error.message}</h2>);
   } 

โดยสรุป จำเป็นต้องจัดเตรียมโค้ดสำหรับส่วนประกอบ JavaScript ที่จะแสดงบนเว็บเบราว์เซอร์ สิ่งนี้ไม่เพียงแต่รับประกันการเรนเดอร์ส่วนประกอบอย่างเหมาะสม แต่ยังช่วยให้เกิดการสื่อสารระหว่างแอปพลิเคชันฝั่งไคลเอ็นต์และฝั่งเซิร์ฟเวอร์ผ่านการใช้เครื่องมือจัดการเหตุการณ์และวิธีการดึงข้อมูล

การใช้ฟังก์ชันการทำงานที่ได้รับจาก hook useQuery คอลเลกชันของโพสต์ที่ดึงข้อมูลจะถูกรวบรวมไว้อย่างมีประสิทธิภาพภายในขอบเขตของตัวแปรข้อมูล การรวมกลุ่มนี้ทำหน้าที่อำนวยความสะดวกในการบำรุงรักษาสถานะภายในของแอปพลิเคชัน ต่อจากนั้น การสำรวจเส้นทางอาจดำเนินการเมื่อมีการสะสมโพสต์ที่จัดเก็บไว้ในตัวแปรดังกล่าว และปิดท้ายด้วยการแสดงภาพบนอินเทอร์เฟซการสืบค้น

เพื่อให้ผู้ใช้สามารถนำทางผ่านข้อมูลแบ่งหน้าเพิ่มเติมได้ จำเป็นต้องรวมปุ่มนำทางสองปุ่มซึ่งมีป้ายกำกับว่า"ก่อนหน้า"และ"ถัดไป"ปุ่มเหล่านี้จะช่วยให้ผู้ใช้สามารถเข้าถึงหน้าเนื้อหาเพิ่มเติมได้เมื่อมีการนำเสนอในรูปแบบเลขหน้า

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

สุดท้ายให้เริ่มเซิร์ฟเวอร์การพัฒนา

 npm run dev 

กรุณาไปที่ “ http://localhost:3000/Pagination ” บนเว็บเบราว์เซอร์ของคุณเพื่อดูคำแนะนำเพิ่มเติม

/th/images/tanstack-query-pagination-example-in-next-js-application.jpg

การรวมองค์ประกอบการแบ่งหน้าภายในโครงสร้างไดเร็กทอรีของแอปพลิเคชันจะแจ้งให้ Next.js จดจำว่าเป็นเส้นทางที่กำหนด ช่วยให้สามารถนำทางไปยังหน้าเว็บที่เกี่ยวข้องได้อย่างราบรื่นผ่าน URL เฉพาะ

การเลื่อนแบบไม่มีที่สิ้นสุดโดยใช้ useInfiniteQuery Hook

การเลื่อนแบบไม่มีที่สิ้นสุดจะสร้างอินเทอร์เฟซผู้ใช้ที่ดูเหมือนไม่สะดุดด้วยการโหลดเนื้อหาเพิ่มเติมแบบไดนามิกในขณะที่ผู้ใช้เลื่อนต่อไป ตัวอย่างที่เป็นแบบอย่างของสิ่งนี้สามารถพบได้ในฟังก์ชันการทำงานของ YouTube โดยที่รายการวิดีโอใหม่จะถูกดึงและนำเสนอได้อย่างง่ายดายโดยไม่มีการหยุดชะงักที่สังเกตได้เมื่อรายการหนึ่งลงมาผ่านหน้าเว็บ

การใช้ฮุค useInfiniteQuery ช่วยให้สามารถเลื่อนดูข้อมูลได้อย่างไม่จำกัดผ่านการดึงข้อมูลจากเซิร์ฟเวอร์ระยะไกล โดยที่หน้าที่ตามมาจะได้รับการร้องขอและแสดงผลโดยอัตโนมัติเมื่อผู้ใช้เลื่อนลงมาเรื่อยๆ

เพื่อรวมฟังก์ชันการเลื่อนแบบไม่มีที่สิ้นสุดภายในแอปพลิเคชันของคุณ จำเป็นต้องสร้างไฟล์ JavaScript ใหม่ชื่อ “InfiniteScroll/page.js” และวางไว้ภายในไดเร็กทอรี “src/app” ต่อจากนั้น คุณจะต้องนำเข้าโมดูลต่างๆ รวมถึงส่วนประกอบ React และไลบรารีการจัดการสถานะ ซึ่งอำนวยความสะดวกในการผสานรวมคุณลักษณะนี้กับโค้ดเบสที่มีอยู่ของคุณได้อย่างราบรื่น

 "use client"
import React, { useRef, useEffect, useState } from 'react';
import { useInfiniteQuery } from '@tanstack/react-query';
import './page.styles.css'; 

ต่อไป เราจะพัฒนาส่วนประกอบการทำงานของ React และภายในนั้น จะสร้างฟังก์ชันที่ดึงเนื้อหาของโพสต์ในลักษณะที่คล้ายคลึงกับวิธีการที่ใช้สำหรับการแบ่งหน้า

 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
} 

ตรงกันข้ามกับการใช้การแบ่งหน้าซึ่งไม่ก่อให้เกิดความล่าช้าในการดึงข้อมูล โค้ดเฉพาะนี้รวมถึงการหยุดชั่วคราวโดยเจตนาประมาณสองวินาทีก่อนที่จะได้รับข้อมูลใหม่ ความล่าช้าโดยเจตนานี้ได้รับการออกแบบมาเพื่อให้ผู้ใช้มีเวลาเหลือเฟือในการเลื่อนและสำรวจชุดข้อมูลปัจจุบัน ซึ่งท้ายที่สุดแล้วส่งผลให้มีการดึงชุดข้อมูลที่อัปเดตโดยอัตโนมัติในขณะที่พวกเขาท่องเว็บต่อไป

เมื่อเริ่มต้น ฮุคแบบกำหนดเอง useInfiniteQuery จะสร้างการเชื่อมต่อกับเซิร์ฟเวอร์ โดยดึงชุดข้อมูลเริ่มต้นเมื่อมีการเรนเดอร์ส่วนประกอบ ต่อจากนั้น เมื่อผู้ใช้เลื่อนดูเนื้อหา hook จะจัดหาชุดข้อมูลตามมาโดยอัตโนมัติ และรวมไว้ในส่วนประกอบโดยไม่ต้องป้อนข้อมูลเพิ่มเติม

   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) : []; 

ตัวแปร โพสต์ ทำหน้าที่เป็นตัวสะสมสำหรับรวบรวมโพสต์ทั้งหมดจากหน้าต่างๆ และทำให้เป็นอาร์เรย์แบบรวม การทำเช่นนี้จะทำให้กระบวนการวนซ้ำและแสดงผลแต่ละโพสต์แยกกันง่ายขึ้น

หนึ่งอาจใช้ฟังก์ชันการทำงานที่ใช้ Intersection Observer API เพื่อตรวจสอบว่าองค์ประกอบเฉพาะเข้ามาภายในขอบเขตของหน้าจอที่มองเห็นของผู้ใช้โดยการกำหนดฟังก์ชัน ซึ่งช่วยให้สามารถตรวจสอบพฤติกรรมการเลื่อนของผู้ใช้และโหลดข้อมูลเพิ่มเติมโดยอัตโนมัติเมื่อใกล้ถึงจุดสิ้นสุดของรายการ

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

โดยสรุป ให้รวมส่วนประกอบ JSX ที่จะแสดงภายในเว็บเบราว์เซอร์

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

เมื่อแก้ไขเสร็จแล้ว โปรดไปที่ “ http://localhost:3000/InfiniteScroll ” เพื่อสังเกตการทำงานโดยตรง

แบบสอบถาม TanStack: มากกว่าแค่การดึงข้อมูล

TanStack Query นำเสนอความสามารถรอบด้านในฐานะไลบรารีการจัดการข้อมูลที่ครอบคลุมผ่านการใช้คุณสมบัติการแบ่งหน้าและการเลื่อนแบบไม่มีที่สิ้นสุด ฟังก์ชันการทำงานเหล่านี้แสดงให้เห็นถึงความสามารถที่หลากหลายที่นำเสนอโดยเครื่องมืออันทรงพลังนี้

โซลูชันนี้เพิ่มประสิทธิภาพการจัดการข้อมูลแอปพลิเคชัน ครอบคลุมการควบคุมสถานะอย่างมีประสิทธิภาพด้วยฟังก์ชันการทำงานที่หลากหลาย เมื่อใช้ร่วมกับการดำเนินการที่เน้นข้อมูลเป็นศูนย์กลางอื่นๆ จะช่วยเพิ่มประสิทธิภาพการดำเนินงานและความพึงพอใจของผู้ใช้แอปพลิเคชันบนเว็บ