- Published on
무한 스크롤 비교
- Authors
- Name
- Hyo814
@tanstack/react-query
의 useInfiniteQuery
사용
1. 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
의 사용 방법을 잘 이해해야 합니다.
@tanstack/react-query
의 useQuery
사용
2. 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
보다 더 많은 코드 작성이 필요합니다.
swr
의 useSWR
사용
3. 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
에 비해 다음 페이지 데이터 처리를 위한 명확한 내장 옵션이 부족합니다.