본문 바로가기

문제 해결하기 - FE

recoil -> Tanstack-query 무한 스크롤 구현

 

안녕하세요. 오늘은 recoil을 이용한 무한스크롤 방식에서 Tanstack-query를 이용한 무한스크롤 방식으로 변경한 이유와 방법에 대해
공유하려고합니다. 부족한 내용이지만 기록을 해보겠습니다. 

 

Recoil에서 사용한 무한스크롤 방법

Recoil에서 사용한 무한스크롤방법은 다음과 같습니다.

1. 데이터 패칭 - 한번에 관련된 모든 데이터를 불러온다. 

2. recoil 캐싱 - 불러온 데이터를 recoil을 이용해서 캐싱처리한다. (SelectorFamily)

3. selector를 이용해서 데이터 slice -  무한스크롤은 fetching Event가 발생할때마다 새로운 데이터를 보여주는 방식이다.
그래서 한번에 받아온 데이터를 한번에 보여주는 것이 아닌, slice를 이용해서 일정부분씩 보여주는 Filter를 사용하였다. 

 

Recoil을 이용해서 구현한 무한 스크롤 로직 

const useInfiniteScroll = ({ initData }: Props) => {
  const [dataSource, setdataSource] = useState<InfiniteScrollData[]>([]);
  const [hasMore, sethasMore] = useState(true);
  const setCount = useSetRecoilState(galleryScrollCountState);
  const [newPage] = useRecoilValue(waitForAll([filteredPageState]));

  useEffect(() => {
    if (initData) {
      setdataSource(initData);
      sethasMore(true);
      setCount(0);
    } else {
      sethasMore(false);
    }
  }, [initData]);

  const fetchMoreData = async () => {
    if (newPage.slicedData.length !== 0) {
      try {
        setTimeout(() => {
          const list = newPage.slicedData;
          if (list) {
            const newList = dataSource.concat(list);
            setdataSource(newList);
            setCount(prev => prev + 1);
          } else {
            console.log('error');
          }
        }, 1500);
      } catch (error) {
        console.error('데이터를 가져오는 중 오류가 발생했습니다:', error);
      }
    } else {
      sethasMore(false);
    }
  };

  return {
    fetchMoreData,
    dataSource,
    hasMore,
  };
};

export default useInfiniteScroll;
export const filteredPageState = selector({
  key: 'InfinitedScrollState',
  get: ({ get }) => {
    const galleryList = get(galleryInfoQuery(get(searchParams)));
    const page = get(galleryScrollCountState);
    const itemCount = get(itemsPerPage);

    if (axios.isAxiosError(galleryList)) {
      return {
        slicedData: [],
        startIndex: 0,
        endIndex: 0,
        state: galleryList,
      };
    }
    if (galleryList === null || galleryList.length <= itemCount) {
      return {
        slicedData: [],
        startIndex: 0,
        endIndex: 0,
        state: galleryList,
      };
    }

    const startIndex = (page + 1) * itemCount;
    const endIndex = startIndex + itemCount;

    const galleryInit = galleryList.slice(0, itemCount);
    const slicedData = galleryList.slice(startIndex, endIndex);
    return { galleryInit, slicedData, startIndex, endIndex };
  },
});
export const galleryInfoQuery = selectorFamily({
  key: 'GallerySelectorFamily',
  get: (params: SearchParams) => async () => {
    const response = await getGallery(params);
    return response;
  },
});

 

 

Recoil에서 Tanstack-query 변경 이유

다음과 같이 구현을 했던 이유는 당시 개발할때는 시간이 부족해서, BE측에서 데이터를 나눠서 보내줄 수 없었습니다. 

그렇기에 FE측에서 데이터를 분할해서 사용하기로 했고 큰 문제는 없었습니다. 

 

하지만 현재는 문제가 없겠지만, 데이터 양이 많이질 수 록 문제가 발생할 수 있는 부분이라 생각했습니다. 

 

 

 

1. 성능 문제: 한 번에 많은 양의 데이터를 불러와서 캐싱하면 초기 로딩 시간이 길어질 수 있습니다. 또한 캐싱된 데이터가 많아지면 메모리 사용량이 증가하여 성능 문제를 야기할 수 있습니다.

 

2. 리소스 소비: 모든 데이터를 미리 불러와서 캐싱하면 서버 및 클라이언트의 리소스를 과도하게 소비할 수 있습니다. 이는 서버 부하와 네트워크 트래픽 증가로 이어질 수 있습니다.

 

3. 네트워크 트래픽 감소: 조금씩 데이터를 가져오는 방식은 한 번에 많은 양의 데이터를 요청하는 것보다 네트워크 트래픽을 줄일 수 있습니다. 이는 사용자가 이동 중이거나 네트워크 연결이 느린 경우에도 페이지를 빠르게 로드할 수 있도록 도와줍니다.

 

4. 코드의 복잡성 : Tanstack Query를 사용하여 무한 스크롤 기능을 구현할 때 코드의 복잡성이 줄어듭니다. useInfiniteQuery를 사용하면 간편하게 무한 스크롤을 구현할 수 있으며, 적은 양의 코드로 원하는 기능을 구현할 수 있습니다. 반면에 Recoil을 사용하여 무한 스크롤을 구현하려면 더 많은 코드가 필요합니다. 데이터의 상태를 관리하고 이를 기반으로 무한 스크롤 동작을 제어해야 하기 때문에 코드가 더 복잡해질 수 있습니다. 따라서 Tanstack Query를 사용하는 것이 코드의 복잡성을 줄이고 무한 스크롤 기능을 더욱 간편하게 구현할 수 있는 장점이 있습니다.

 

 

구현 로직 (Tanstack-query - useInfiniteQuery)

 

 

 

구현하기 

기능적 내용 - UseInfiniteGalley Hooks

input :  max_page_per_count, filter

output :  data.pages, data.pageParams ... 

 

기능 정리

input max_page_per_count number 페이지당 item 갯수
filter select : number
type : string
category : string
각 항목별 필터 
output data pages:TData[]  모든 페이지를 포함하는 배열 (페이지 마다 아이템 리스트 배열) 
  pageParams  모든 페이지 매개변수를 포함하는 배열
isLoading boolean 데이터 처음 캐싱처리가 완료 됬는지 
fetchNextPage (option? :FetchNextPageOptions) => Promise<UseInfiniteQueryResult) 다음 페이지 정보 불러오기 
hasNextpage boolean 다음 페이지가 있는지 정보 
     

 

 

기능적 내용 - getInfiniteGallery (http 메서드 GET) 

input pageParams number 페이지 index (현재 페이지 순서) 
filter select : number
type : string
category : string
각 항목별 필터 
size number 페이지 당 item 갯수
output result GalleyItem[] 갤러리 아이템 정보
error error 에러 정보 

 

 

주고 받을 데이터 양식

{
  "title": "테스트입니다.",
  "url": "https://626386e1-b34a-4f72-a682-2469833474b1",
  "memberUrl": "https://profile.png",
  "memberId": 7,
  "author": "홍길동",
  "id": 17,
  "isLike": false,
  "likeNum": 0
},
{
  "title": "이불 속이 안좋아",
  "url": "https://626386e1-b34a-4f72-a682-2469833474b1",
  "memberUrl": "https://profile.png",
  "memberId": 2,
  "author": "이순신",
  "id": 7,
  "isLike": true,
  "likeNum": 4
},

 

 

Tanstack-query useInfiniteQuery 사용시에 겪었던 점

1. queryFn에 사용할 http 요청 함수에는 pageParam이 꼭 있어야한다.  (버전확인도 잘하기)
공식 홈페이지를 확인하면 나오긴 하지만 잘봐야한다.

TanStack query v5

 

TanStack query v4

 

TanStack query를 사용한다고 하면 꼭 버전확인을 잘해야된다. 공식문서를 보고 완성한다고 하면 문제는 없겠지만, 

블로그같은 다른사람의 글을 보고 작성한다고 했을 때 문제가 발생할 수도 있다. 

 

TanStack query v4 -> v5로 가면서 변경된 내용이 있다. 

initialPageParam: TPageParam
Required

initialPageParam이 이제 필수값으로 적용됬다. 만약에 이거를 빠트리게 된다면 문제가 발생할 수도 있으니 주의하자. 

 

2. infiniteQuery를 사용할 때 pageParams가 꼭 필요하다. 
useInfiniteQuery는 QueryFunctionContext를 입력값으로 받는데, QueryFunctionContext는 infiniteQuery를 사용할 때 pageParam을 매개변수로 받는다. 이건 꼭 주의해줘야된다. 

 

 

 

정리

무한스크롤 구현을 여러 방식을 걸쳐서 정리한거같다. 다양한 방법이 있지만 더 효율적이고 코드를 간결하게 할 수 있도록 변경해가는것은 좋은거같다. 팀웍크를 위해서도 코드는 최대한 간결하고 가독성있게 변경해 가도록 해야겠다. 

코드와 예시영상은 추후 올릴 예정이다.