[프로젝트] useInfiniteQuery 대신 수동으로 무한스크롤 구현

React-queryReactJS
3/5/2023

파라미터 수난기

그냥 page parameter만 드리고 싶었어요…

그러나 백엔드 개발자분께서 다음 페이지의 데이터를 받으려면, 수정일 기준 다음 데이터의 idx와,,,
 

최초 요청은 limit만 넘기면 됩니다

  • limit은 화면 레이아웃을 고려하여 9로 constant로 고정값!
notion image
 

그러나 두 번째 페이지를 받고 싶으냐? 그렇담 내게 이것과 이것과 이것을 다오….

  • 첫 번째 요청에서 받았던 nextRevisionDateIdx와 nextIdx를 드려야 한다.
    • Q1. 왜 page가 아니라 revisionDateIdx 를 보내드려야 하나요?
      • 최신 수정일을 기준으로 데이터를 나열하기 때문에
        • 그 사이에 수정된 A데이터가 등장! 그렇다면 A데이터가 제일 최신으로 서버에서 업데이트 됨
        • 그러나 react-query는 cache time이 아직 유효하므로, refetch하지 않을 것
        • 이 상태에서 next page 데이터를 요청한다면 호출되지 않는 데이터가 생겨남
          • 그래서 바로 이전의 데이터에서 가진 nextRevisionDateIdx를 호출해야 누락없이 다음 데이터를 호출할 수 있음
          • 그러나 수정일이 바뀌어도 제일 앞에 추가되지 중간에 데이터가 삽입되지 않음
            • 최초 수정일 기준은 앞으로의 next page 호출 동안 유지가능
            • 제일 앞에 업데이트 된 데이터는 아예 페이지를 새로고침하여야 업데이트 되어야 함
      • 첫번째 요청에서 받았던 그 수정일을 고정하면 되지 않나요?
        • 첫 데이터 기준 다음 데이터를 요청하는데, 이전 데이터의 다음 데이터가 중복이나 제외없이 보내지도록 사용해야 함
        •  
    • Q2. nextIdx는 또 무엇인가요?
      • RevisionDateIdx도, idx의 차이가 무엇인지 궁금해요
      • revision: 수정일 기준 이후의 것
      • idx: 수정일이 동일한 작품을 구별하기 위해
notion image
 
 

 

그래 잘가, useInfiniteQuery!

  • 결국 수정일 idx 기준으로 파라미터를 요청할 수밖에 없고
  • 쿼리가 나에게 선물해주려고 했던 useInfiniteQuery를 사용할 수 없게 되었다
  • 그렇다면 나는 알아서 무한 요청을 구현하면 된다! (잘가 …. 😇)
 

다시 짚고 가기! - 무한 스크롤이 무엇인가?

  • 다량의 데이터를 한번에 다운로드받아 노출하지 않고, 페이지 단위로 데이터 일부를 보여주고, 유저가 스크롤을 내려 추가 데이터를 원할 때마다 다음 페이지 데이터를 호출하는 방식
  • 왜 쓰는가?
    • 필요없는 트래픽을 줄이기 위해서
    • 필요한 만큼만 데이터를 불러와 최대한 빠르게 유저에게 데이터를 보여주고 싶어서
  • 어떻게 구현하는가?
      1. 우선 백엔드에 페이지 단위로 데이터를 잘라서 받을 수 있는 api를 요청한다 : 파라미터로 page 등을 활용할 수 있다면 react-query에서 제공하는 useInfinitequery를 사용할 수 있으므로, 사전에 설계할 때 이를 주안점에 두고 논의하면 좋다! (수정일 기준으로 호출하는 로직을 이미 작성해서 바꾸기가 어렵다는 백엔드 개발자님의 요청에, 나는 이걸 쓸 수 없었지만,,,)
      1. 프론트에서는 한 회 요청마다 데이터를 받아올 수 있도록 페이지 당 데이터 개수 limit을 지정하여 api(GET) 로직을 작성한다
      1. 뷰포트와 특정 영역(ref)의 intersection을 감지할 수 있도록 react-intersection-observer의 useInView가 반환하는 ref를 last data를 담당하는 태그에 지정한다.
      1. useInView가 반환하는 InView(boolean)을 활용하여, 특정 영역이 감지될 때마다 다음 데이터가 있다면 다음 데이터를 호출할 수 있도록 nextQuery 상태값을 업데이트한다. → useGetImages의 내부 상태가 업데이트 되었으므로, useQuery는 새로워진 상태값을 활용하여 다음 페이지 데이터를 호출함
      1. 쌓여진 데이터인 allImages를 사용처, 즉 Gallery 컴포넌트에 보내주어 이전 데이터와 함께 새로 호출된 데이터를 렌더링하여 보여준다
// consume const Gallery = () => { const { data, isLoading, setNextQuery, allImages } = useGetImages(); const { ref: intObserver, inView } = useInView({ threshold: 0.3 }); useEffect(() => { if (!window.scrollY) return; //최초 렌더링시 일어나는 intersection 무시 if (data?.hasNext) { setNextQuery({ nextRevisionDateIdx: data.nextRevisionDateIdx, nextIdx: data.nextIdx }); } }, [inView]); const GalleryProps: TGalleryProps = { thumbnails: allImages, isLoading }; return <GalleryView {...GalleryProps} ref={intObserver} />; }; export default Gallery;
 
const useGetImages = () => { // 페이지별 데이터 호출 -> 축적된 페이지 데이터를 모아두는 allImages const [allImages, setAllImages] = useState< TArtImageResponse[`artImageResponses`] >([]); // const [nextQuery, setNextQuery] = useState<{ nextRevisionDateIdx: string; nextIdx: number; }>(); // nextRevisionDateIdx로 페이지 데이터 캐싱 관리 const { data, isLoading } = useQuery< AxiosResponse, AxiosError, TArtImageResponse >( [...CACHE_KEYS.images, nextQuery?.nextRevisionDateIdx], () => { return getImages(PER_PAGE, nextQuery?.nextRevisionDateIdx, nextQuery?.nextIdx); }, { staleTime: 500, select: (data) => data.data, onSuccess(data) { setAllImages((prev) => [...prev, ...data.artImageResponses]); } } ); return { data, isLoading, setNextQuery, allImages }; }; export default useGetImages;

알아야 할 것 _ React.forwardref

  1. 고차 컴포넌트
    1. 컴포넌트가 자신의 ref를 자식 중 하나에게 전달할 수 있는 컴포넌트
    2. 부모 컴포넌트가 자식 컴포넌트의 DOM 노드와 직접적으로 상호작용하길 바랄 때 사용함
  1. 자식 컴포넌트가 forwardRef가 되는 것!
    1. const ChildComponent = React.forwardRef((props, ref) => { return <input type="text" ref={ref} />; //부모한테서 전달받은 ref를 사용함 }); const ParentComponent = () => { const inputRef = useRef(null); //자식한테 줄 ref를 생성함 const handleClick = () => { inputRef.current.focus(); }; return ( <> <ChildComponent ref={inputRef} /> <button onClick={handleClick}>Focus input</button> </> ); };
       
      💡
      useRef - 렌더 사이에서 유지한 값을 저장하는 데 사용하는 mutable ref object를 반환하는 훅 - 주로 DOM 노드에 대한 참조값을 저장
      💡
      차이점이 뭔데? forwardRef와 달리, useRef는 해당 ref를 자식 컴포넌트에 전달하길 허락하지 않아!
 
©JIYOUNG CHOI, All rights reserved