Published on

무한 스크롤 비교

Authors
  • avatar
    Name
    Hyo814
    Twitter

1. @tanstack/react-queryuseInfiniteQuery 사용

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-queryuseQuery 사용

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. swruseSWR 사용

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에 비해 다음 페이지 데이터 처리를 위한 명확한 내장 옵션이 부족합니다.