--b52884fcf6e7eecb x-next-cache-tags: _N_T_/layout,_N_T_/blog/layout,_N_T_/blog/[...slug]/layout,_N_T_/blog/[...slug]/page,_N_T_/blog/%EB%AC%B4%ED%95%9C-%EC%8A%A4%ED%81%AC%EB%A1%A4-%EB%B9%84%EA%B5%90 vary: RSC, Next-Router-State-Tree, Next-Router-Prefetch 무한 스크롤 비교 | Hyo814`s Blog
Published on

무한 스크롤 비교

Authors
  • avatar
    Name
    Hyo814
    Twitter

1. @tanstack/react-query의 useInfiniteQuery 사용

import React, { useEffect, useRef } from 'react';
import { useInfiniteQuery, QueryFunctionContext } from '@tanstack/react-query';

interface Photo {
    id: number;
    title: string;
    url: string;
    thumbnailUrl: string;
}

const fetchPhotos = async ({ pageParam = 1 }: QueryFunctionContext): Promise<Photo[]> => {
    const res = await fetch(`https://jsonplaceholder.typicode.com/photos?_page=${pageParam}&_limit=10`);
    return res.json();
};

const InfiniteScroll: React.FC = () => {
    const { data, error, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfiniteQuery<Photo[], Error>({
        queryKey: ['photos'],
        queryFn: fetchPhotos,
        getNextPageParam: (lastPage, allPages) => lastPage.length ? allPages.length + 1 : undefined,
    });

    const observerRef = useRef<HTMLDivElement | null>(null);

    useEffect(() => {
        const observer = new IntersectionObserver((entries) => {
            if (entries[0].isIntersecting && hasNextPage) {
                fetchNextPage();
            }
        });

        if (observerRef.current) {
            observer.observe(observerRef.current);
        }

        return () => {
            if (observerRef.current) {
                observer.unobserve(observerRef.current);
            }
        };
    }, [fetchNextPage, hasNextPage]);

    if (error) return <div>Failed to load</div>;

    return (
        <div>
            <h1>Infinite Scroll</h1>
            <div>
                {data?.pages.map((page, i) => (
                    <React.Fragment key={i}>
                        {page.map((photo: Photo) => (
                            <div key={photo.id}>
                                <img src={photo.thumbnailUrl} alt={photo.title} />
                                <p>{photo.title}</p>
                            </div>
                        ))}
                    </React.Fragment>
                ))}
            </div>
            {isFetchingNextPage && <p>Loading more...</p>}
            <div ref={observerRef} style={{ height: '1px' }}></div>
        </div>
    );
};

export default InfiniteScroll;
  • 장점
    • 내장된 페이지네이션 처리로 무한 스크롤 구현이 간단.
    • getNextPageParam으로 다음 페이지 매개변수 설정.
    • isFetchingNextPage 상태로 로딩 상태 관리 가능.
  • 단점
    • react-query 사용법 숙지가 필요.

2. @tanstack/react-query의 useQuery 사용

import React, { useEffect, useRef, useState } from 'react';
import { useQuery } from 'react-query';

interface Photo {
    id: number;
    title: string;
    url: string;
    thumbnailUrl: string;
}

const fetchPhotos = async (page: number): Promise<Photo[]> => {
    const res = await fetch(`https://jsonplaceholder.typicode.com/photos?_page=${page}&_limit=10`);
    return res.json();
};

const InfiniteScroll: React.FC = () => {
    const [page, setPage] = useState<number>(1);
    const [photos, setPhotos] = useState<Photo[]>([]);
    const observerRef = useRef<HTMLDivElement | null>(null);

    const { data, error, isLoading } = useQuery(['photos', page], () => fetchPhotos(page), { keepPreviousData: true });

    useEffect(() => {
        if (data) {
            setPhotos((prevPhotos) => [...prevPhotos, ...data]);
        }
    }, [data]);

    useEffect(() => {
        const observer = new IntersectionObserver((entries) => {
            if (entries[0].isIntersecting) {
                setPage((prevPage) => prevPage + 1);
            }
        });

        if (observerRef.current) {
            observer.observe(observerRef.current);
        }

        return () => {
            if (observerRef.current) {
                observer.unobserve(observerRef.current);
            }
        };
    }, []);

    if (error) return <div>Failed to load</div>;

    return (
        <div>
            <h1>Infinite Scroll</h1>
            <div>
                {photos.map((photo) => (
                    <div key={photo.id}>
                        <img src={photo.thumbnailUrl} alt={photo.title} />
                        <p>{photo.title}</p>
                    </div>
                ))}
            </div>
            {isLoading && <p>Loading...</p>}
            <div ref={observerRef} style={{ height: '1px' }}></div>
        </div>
    );
};

export default InfiniteScroll;
  • 장점
    • 간단한 페이지네이션 로직 구현 가능.
    • 상태를 useState로 관리하여 코드 가독성 높음.
  • 단점
    • useInfiniteQuery에 비해 많은 코드 작성 필요.
    • 무한 스크롤 로직 수동 처리.

3. SWR의 useSWR 사용

import React, { useEffect, useRef, useState } from 'react';
import useSWR from 'swr';

interface Photo {
    id: number;
    title: string;
    url: string;
    thumbnailUrl: string;
}

const fetcher = (url: string) => fetch(url).then((res) => res.json());

const InfiniteScroll: React.FC = () => {
    const [page, setPage] = useState<number>(1);
    const [photos, setPhotos] = useState<Photo[]>([]);
    const observerRef = useRef<HTMLDivElement | null>(null);

    const { data, error } = useSWR<Photo[]>(`https://jsonplaceholder.typicode.com/photos?_page=${page}&_limit=10`, fetcher);

    useEffect(() => {
        if (data) {
            setPhotos((prevPhotos) => [...prevPhotos, ...data]);
        }
    }, [data]);

    useEffect(() => {
        const observer = new IntersectionObserver((entries) => {
            if (entries[0].isIntersecting) {
                setPage((prevPage) => prevPage + 1);
            }
        });

        if (observerRef.current) {
            observer.observe(observerRef.current);
        }

        return () => {
            if (observerRef.current) {
                observer.unobserve(observerRef.current);
            }
        };
    }, []);

    if (error) return <div>Failed to load</div>;

    return (
        <div>
            <h1>Infinite Scroll</h1>
            <div>
                {photos.map((photo) => (
                    <div key={photo.id}>
                        <img src={photo.thumbnailUrl} alt={photo.title} />
                        <p>{photo.title}</p>
                    </div>
                ))}
            </div>
            <div ref={observerRef} style={{ height: '1px' }}></div>
        </div>
    );
};

export default InfiniteScroll;
  • 장점
    • 자동 데이터 캐싱 및 가져오기 관리.
    • 간단한 설정으로 구현 가능.
  • 단점
    • 추가 로직 필요.
    • react-query보다 명확한 내장 옵션 부족
--b52884fcf6e7eecb--