리액트 무한 스크롤 구현
-
무한 스크롤 구현방법에는 2가지가 있다.
- scroll 이벤트를 통해 페이지 마지막부분으로 scroll이 되었을경우 새로운 page를 fetching하는 방법.
- 페이지 마지막 부분에 div태그를 두고 Intersection Observer를 통해 해당 div가 화면에 보일때 새로운 page를 fetching 하는 방법
두가지 방법의 차이점과 어느게 더 좋은지에 관한것은 다른 블로그에도 많으니 생략하겠다.
- scroll 이벤트를 통해 페이지 마지막부분으로 scroll이 되었을경우 새로운 page를 fetching하는 방법.
-
리액트에서 Intersection Observer를 편하게 사용하기 위한 라이브러리가 있다.
react-intersection-observer를 사용해보자.
123456789101112131415161718192021import React from 'react';import { useInView } from 'react-intersection-observer';const Component = () => {const { ref, inView, entry } = useInView({/* Optional options */// threshold: 0,// rootMargin:"50px"});return (<div><ul>.......</ul><div ref={ref}><h2>{`Header inside viewport ${inView}.`}</h2></div></div>);};cs ref를 페이지 마지막 div에 넣고 해당 div가 뷰포트 안에 들어오면 inView 값이 true가 되는 방식이다.
-
useInfiniteQuery를 사용해보자.
이제 inView값이 true 일때 useInfiniteQuery가 다음 page를 Fetching 하게 되면 된다.
123456789101112131415161718192021...import { useInfiniteQuery } from "@tanstack/react-query";const Component = () => {....const { isFetchingNextPage, fetchNextPage } = useInfiniteQuery(['query-key'], fetchFc, {enabled: false, // 자동으로 fetch가 되는것을 방지하고 오직 fetchNextPage함수에 의해서만 fetch 되도록 하기 위해getNextPageParams: (lastPage, allPages) => lastPage.page < lastPage.totalPage ? lastPage.page + 1 : undefined,// 마지막 페이지가 아닐경우 다음 페이지 넘버를 반환하고 마지막 페이지일 경우 undefined를 반환한다.});useEffect(() => {if (inView && !isFetchingNextPage) {fetchNextPage()}}, [inView, isFetchingNextPage])return ...}cs fetchFn에 pageParam이 전달된다.
1234const fetchFn = ({ pageParam = 1}) => {return fetch(`/api/getsomething?page=${pageParam}`).then(response => response.json())}cs 하지만 가장처음 호출될때는 undefined가 전달된다. 한번도 호출된적이 없기 때문에 getNextPageParam에서 사용될 lastPage가 없기 때문이다. 그래서 default값으로 1을 넣어준다. 참고
이 방법에는 문제점이 있다.
실제로 fetchNextPage가 실행되는 순간 isFetchingNextPage의 값이 true가 되기전에 바로 fetchNextPage가 또한번 더 실행된다. fetchNextPage가 비동기함수로써 실행되기까지 시간이 걸리기 때문이다. (isFetchingNextPage 값이 true가 되기까지 시간이 걸린다. 그전에 또다시 fetchNextPage가 실행된다)
-
useState를 통해 상태를 관리해보자.
12345678910111213141516171819202122232425262728...import { useState} from 'react';import { useInfiniteQuery } from "@tanstack/react-query";const Component = () => {....const [data, setData] = useState([]);const [isLoading, setIsLoading] = useState(false);const { fetchNextPage } = useInfiniteQuery(['query-key'], fetchFc, {...onSuccess: (newData) => {setData(newData.pages.map(pages.results).flat());setIsLoading(false); // 다시 fetchNextPage가 가능할 수 있도록 해준다.}});useEffect(() => {if (inView && !isLoading) {setIsLoading(true);fetchNextPage();}}, [inView, isLoading])return ...}cs 위의 경우에는 fetchNextPage가 동시에 여러번 수행되지는 않는다. 하지만 fetch가 끝나고나서 setIsLoading(false)가 호출되는 순간 실제로 화면에 다시 렌더링되면서 화면에 새로운 li 엘레멘트들이 paint되기전에 inView가 트리거되어 또한번 fetchNextPage가 호출된다. 즉, 의도치 않은 fetching이 또 일어난다.
-
useRef를 통해 상태관리를 해보자.
123456789101112131415161718192021222324252627...import { useState} from 'react';import { useInfiniteQuery } from "@tanstack/react-query";const Component = () => {....const isLoading = useRef(false);const { fetchNextPage } = useInfiniteQuery(['query-key'], fetchFc, {...onSuccess: (newData) => {...isLoading.current = false; // 다시 fetchNextPage가 가능할 수 있도록 해준다.}});useEffect(() => {if (inView && !isLoading.current) {isLoading.current = true;fetchNextPage();}}, [inView, isLoading])return ...}cs 이제 깔끔하다. 원하는대로 infinite scroll이 구현되었다.
No comments:
Post a Comment