TIL

무한 스크롤 정리 2

효ㄷi 2024. 7. 16. 20:56

무한 스크롤 정리2

 

이제 무한 스크롤을 본격적으로 써먹어 봐야한다.

 

const getPokemons = async ({ pageParam = 1 }) => {
  const res = await fetch(`http://localhost:3000/api/shop?page=${pageParam}`);
  const data = await res.json();
  return data;
};

이전에 써먹었던 코드를 갖고와야 한다. 근데 페이지 번호를 받아와야 함.

기본적으로 페이지 번호를 1로 설정하고, 주어진 페이지 번호를 사용해 해당 페이지의 데이터를 서버에서 가져오는 과정이다.

 

const {
  data: pokemons,
  fetchNextPage,
  hasNextPage,
  isFetchingNextPage
} = useInfiniteQuery({
  queryKey: ['pokemonsQuery'],
  queryFn: getPokemons,
  getNextPageParam: (lastPage) => lastPage.nextPage ?? undefined,
  initialPageParam: 1
});

이제 useInfiniteQuery를 사용해 무한 스크롤을 구현한다.

먼저 데이터를 pokemons라는 이름으로 사용한다.

fetchNext는 다음 페이지의 데이터를 가져온다.

hasNextPage는 다음 페이지가 있는지 확인하는 값이다.

isFetchingNextPage는 다음 페이지를 가져오는지 확인하는 값이다.

 

이제 쿼리를 구별하기 위해 사용하는 쿼리키, 데이터를 가져오는 방법을 정의하는 쿼리 함수를 각각 써주고,

다음 페이지를 어떻게 가져올지 정해줘야 한다.

 

getNextPageParam은 다음 페이지의 데이터를 가져올 때 사용할 페이지 번호를 결정하고,

두 가지 인자를 받을 수 있다.

바로 lastPage와 allPages 혹은 page다.

이것이 무한 스크롤의 핵심인 듯...

 

하여튼 lastPage는 마지막으로 가져온 페이지으 ㅣ데이터다.

여기서 lastPage.nextPage를 사용하는데, 이것은 마지막 페이지 데이터에서 다음 페이지 번호를 가져온다.

(이 객체에 다음 페이지 번호와 관련된 정보가 포함되어 있을 수 있다는 듯)

 getNextPageParam: (lastPage) => lastPage.nextPage ?? undefined,

 

 

그래서 마지막 페이지에 다음 페이지 번호가 없다면 undefined를 반환하라는 얘기다.

 

추가로 page는 현재 페이지 번호인데

모든 페이지의 길이를 사용하여 다음 페이지를 결정할 때 allPages가 필요하고,

일부 페이지까지만 데이터를 가져오고 싶을 때 page를 사용하는... (것 같ㄷ ㅏ안써서 모름)

 

이제 initialPageParam인데 이거랑 pageParam이랑 좀 헷갈렸다.

initialPageParam은 무한 스크롤의 초기 상태를 명확히 설정할 때 좋고,

pageParam은 getPokemons 함수를 여러 곳에서 재사용 할 때 좋다는 듯.

 

둘 다 같이 써도 된다.

 

const { ref, inView } = useInView();

  useEffect(() => {
    if (inView && hasNextPage) {
      fetchNextPage();
    }
  }, [inView, fetchNextPage, hasNextPage]);

이제 이 부분인데,

 

useInview는 특정 요소가 화면에 보이는지 확인하는데 사용한다.

이것도 무한 스크롤 기능에서 중요한 역할을 하는데

react-intersection-observer라는 라이브러리에서 제공하고, 스크롤을 감지하는데 유용하다.

 

useInview의 역할은 요소가 화면에 보이는지 감지하고, 무한 스크롤을 구현한다.

 

먼저 useInview 훅을 사용해 ref와 inview를 가져온다.

그리고 이 ref를 스크롤의 끝부분을 나타내는 요소에 할당한다.

이 요소가 화면에 들어올 때 다음 페이지으 ㅣ데이터를 가져오도록 한다.

        {isFetchingNextPage ? <div className="my-2 text-center text-2xl">로딩중...</div> : <div ref={ref}></div>}

이렇게 말이죠...

isFetchingNextPage -> 다음 페이지를 가져오는 중인지 여부를 나타내는 불리언 값이 true면 데이터를 가져오는 것이고

false면 데이터를 가져오는 것이 아니다.

 

그래서 true면 로딩중을 화면에 띄워주고, false면 ref가 할당된 빈 div 요소를 렌더링해서,

이 요소가 화면에 보일 때 useInview 훅이 inview를 true로 설정해 다음 페이지 데이터를 가져온다.

 

  useEffect(() => {
    if (inView && hasNextPage) {
      fetchNextPage();
    }
  }, [inView, fetchNextPage, hasNextPage]);

이제 useEffect 훅을 사용해, inview (요소가 화면에 보이면 true), hasNextPage (다음 페이지가 있는 경우에만 데이터 가져옴) 둘 다 조건을 만족하면 fetchNextPage(다음 페이지의 데이터 가져옴) 함수를 호출한다.

 

 

 

아놔 뿌려주는 걸 까먹음

 

이제 map으로 돌려서 뿌려주면 되는데

{pokemons?.pages.map((page) =>
  page.result.map((pokemon: Pokemon) => <PokeCard key={pokemon.id} pokemon={pokemon} />)
)}

페이지를 순회하면서 돌려야 하는 것 같다.

그래서 pokemons 객체 안에 있는 pages 배열(여러 페이지의 데이터를 담고 있음)을 돌려주는데,

각 페이지 안에 result 배열(해당 페이지의 포켓몬 데이터를 담고 있음)을 또 돌려준다.

그리고 각 포켓몬 객체를 기반으로 pokeCard라는 컴포넌트에 데이터를 전달해줌.