이 글에서는 React Query를 사용하여 피드 페이지를 구축하는 방법을 살펴보겠습니다!
우리가 만들 내용은 다음과 같습니다.
이 문서에서는 앱 구축과 관련된 모든 단계와 세부 사항을 다루지는 않습니다.
대신 핵심 기능, 특히 "무한 스크롤" 및 "맨 위로 스크롤" 기능에 중점을 둘 것입니다.
전체 구현에 대한 컨설팅에 관심이 있다면 이 GitHub 저장소에서 전체 코드베이스를 찾을 수 있습니다.
먼저 다음 명령을 사용하여 Vite를 사용하여 React 애플리케이션을 만듭니다.
npm create vite@latest feed-page-rq -- --template react-ts
그리고 필요한 종속성인 axios 및 react-query:
를 설치합니다.
npm install axios @tanstack/react-query@4
RESTful 서버도 모의해야 하므로 json-server를 사용하겠습니다. 이를 통해 React 앱에 가짜 API 엔드포인트를 제공하여 백엔드를 시뮬레이션할 수 있습니다.
우리는 다음 속성을 포함하는 게시물 엔터티로 작업할 것입니다:
{ "id": "1", "title": "Sample Post Title", "body": "This is a sample post body", "userId": "2", "createdAt": 1728334799169 // date timestamp }
서버가 설정되면 다음을 사용하여 서버를 실행합니다.
npx json-server --watch db.json
"무한 스크롤" 기능의 메커니즘은 간단합니다.
사용자가 게시물 목록을 스크롤하고 컨테이너 하단에 접근하면 React Query는 다음 게시물 배치를 찾습니다. 더 이상 로드할 게시물이 없을 때까지 이 프로세스가 반복됩니다.
현재 스크롤 위치(scrollTop)를 표시 화면 높이(클라이언트 높이)에 추가하고 이 합계를 와 비교하여 사용자가 하단 근처에 있는지 확인합니다. 컨테이너의 총 높이(scrollHeight).
합계가 전체 컨테이너 높이보다 크거나 같으면 React Query에 다음 페이지를 가져오도록 요청합니다.
const { scrollTop, scrollHeight, clientHeight } = elemRef.current; if(scrollTop clientHeight >= scrollHeight) { fetchNextPage(); }
먼저 React Query의 useInfiniteQuery를 래핑하기 위한 사용자 정의 후크를 생성합니다.
사용자 정의 후크 내에서 초기 페이지 번호와 다음 페이지를 검색하는 함수를 지정하여 페이지별로 게시물을 가져오도록 쿼리를 구성합니다.
import { QueryFunctionContext, useInfiniteQuery } from "@tanstack/react-query"; import axios from "axios"; const URL = "http://localhost:3000"; const POSTS = "posts"; export const fetchInfinitePosts = async ({ pageParam, }: QueryFunctionContext) => { const result = await axios.get( `${URL}/${POSTS}?_sort=-createdAt&_page=${pageParam}&_per_page=10`, ); return result.data; }; export const useInfinitePosts = () => { return useInfiniteQuery({ queryKey: [POSTS], queryFn: fetchInfinitePosts, initialPageParam: 1, getNextPageParam: (lastPage) => lastPage.next, }); };
이제 구성요소의 사용자 정의 후크를 사용하여 게시물 목록을 표시하겠습니다.
이를 위해 먼저 페이지를 반복한 다음 각 페이지 내의 게시물을 반복하여 렌더링합니다.
import { useInfinitePosts } from './hooks/useInfinitePosts'; const PostList = () => { const { data: postLists } = useInfinitePosts(); return ({postLists?.pages.map((page) => page.data.map(post => (); }; export default PostList;)) )}{post.title}
{post.body}
무한 스크롤 동작을 구현하려면 게시물이 표시되는 컨테이너에 스크롤 이벤트 리스너를 추가해야 합니다. 이 이벤트 리스너는 사용자가 컨테이너 하단 근처에 있는지 확인하고 그렇다면 fetchNextPage를 호출하여 더 많은 콘텐츠를 로드하는 onScroll 함수를 트리거합니다.
import React, { useRef, useEffect } from 'react'; import { useInfinitePosts } from './hooks/useInfinitePosts'; const PostList = () => { const { data: postLists, fetchNextPage } = useInfinitePosts(); const elemRef = useRef(null); const onScroll = useCallback(() => { if (elemRef.current) { const { scrollTop, scrollHeight, clientHeight } = elemRef.current; const isNearBottom = scrollTop clientHeight >= scrollHeight; if(isNearBottom) { fetchNextPage(); } } }, [fetchNextPage]); useEffect(() => { const innerElement = elemRef.current; if (innerElement) { innerElement.addEventListener("scroll", onScroll); return () => { innerElement.removeEventListener("scroll", onScroll); }; } }, [onScroll]); return ({postLists?.pages.map((page, i) => page.data.map(post => (); }; export default PostList;)) )}{post.title}
{post.body}
다음으로 새 게시물이 추가될 때 표시되는 '맨 위로 스크롤' 버튼을 만들어 보겠습니다. 이 버튼을 사용하면 사용자가 맨 위로 빠르게 돌아가 최신 업데이트를 볼 수 있습니다.
게시물은 생성 날짜순으로 정렬되므로 새로 추가된 게시물은 목록 상단에 표시됩니다.
우리 기능의 로직은 이 전제 위에 구축될 것입니다.
가장 최근에 작성된 게시물을 가져오고 캐시하는 새 쿼리를 만드는 것부터 시작합니다. 이 게시물을 prevNewestPost라고 하겠습니다.
우리는 prevNewestPost가 목록의 첫 번째 게시물과 몇 단계 뒤처지거나 최대로 일치하도록 하려고 합니다. 따라서 다시 가져오기를 수동으로 제어하겠습니다.
쿼리 옵션에서 available: false를 설정하여 이를 달성합니다.
export const fetchNewestPost = async () => { const result = await axios.get(`${URL}/${POSTS}?_sort=-createdAt`); return result.data[0]; }; export const useNewestPost = () => { return useQuery({ queryKey: [POSTS, "newest"], queryFn: () => fetchNewestPost(), enabled: false, }); };
React Query를 사용하면 특정 이벤트에 대해 게시물 목록이 자동으로 업데이트됩니다. (이러한 이벤트의 전체 목록에 대한 문서 링크는 다음과 같습니다.)
이 업데이트된 목록을 사용하여 prevNewestPost와 첫 번째 게시물을 비교하여 '맨 위로 스크롤' 버튼을 표시할 시기를 결정합니다.
서로 다른 경우에는 새로운 글이 추가된 것이므로 '맨 위로 스크롤' 버튼이 표시됩니다.
setIsShowButton(postLists?.pages[0].data[0].id !== prevNewestPost?.id);
사용자가 게시물 목록 컨테이너 상단에 있을 때 "맨 위로 스크롤" 버튼을 표시하면 안 됩니다.
따라서 사용자가 최상위에 도달하면 쿼리 다시 가져오기를 트리거하여 prevNewestPost를 현재 최신 게시물과 다시 동기화해야 합니다.
const { data: prevNewestPost, refetch } = useNewestPost(); const [isShowButton, setIsShowButton] = useState(false); useEffect(() => { if (!isNearTop) { setIsShowButton(postLists?.pages[0].data[0].id !== prevNewestPost?.id); } else { setIsShowButton(false); refetch(); } }, [postLists, prevNewestPost, isNearTop]);
ToTopBtn 버튼을 클릭하면 목록 상단으로 스크롤되어 버튼을 숨기고 데이터를 다시 가져와 prevNewestPost를 목록의 첫 번째 게시물과 동기화하는 기존 로직이 트리거됩니다.
import { RefObject } from "react"; type ToTopBtnProps = { elemRef: RefObject; }; export default function ToTopBtn({ elemRef }: ToTopBtnProps) { return ( ); }
'맨 위로 스크롤' 버튼 기능을 테스트하려면 피드에 새 게시물을 추가해야 합니다.
이를 위해 React Query의 useMutation을 사용하여 서버에 새 게시물을 추가하고 각 변형 후에 캐시된 postList의 유효성을 다시 검사합니다.
사용자가 버튼을 클릭할 때마다 무작위 데이터로 새 게시물을 만들 수 있는 변형을 설정하겠습니다.
export const savePost = async (post: NewPostData) => axios.post(`${URL}/${POSTS}`, post); export const useAddPost = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: savePost, onSuccess: () => { queryClient.invalidateQueries({ queryKey: [POSTS] }); }, }); };
export default function AddNewPostBtn() { const mutation = useAddPost(); return ();
이 튜토리얼에서는 실제 사용 사례를 통해 React Query의 강력한 기능을 살펴보고 개발자가 사용자 경험을 향상시키는 동적 인터페이스를 구축하는 데 도움이 되는 기능을 강조했습니다.
부인 성명: 제공된 모든 리소스는 부분적으로 인터넷에서 가져온 것입니다. 귀하의 저작권이나 기타 권리 및 이익이 침해된 경우 자세한 이유를 설명하고 저작권 또는 권리 및 이익에 대한 증거를 제공한 후 이메일([email protected])로 보내주십시오. 최대한 빨리 처리해 드리겠습니다.
Copyright© 2022 湘ICP备2022001581号-3