이번 주 항해 취업 리부트코스에서 내가 구현한 기능은 무엇인가요?
해당 기능을 구현하기 위해, 어떤 기술적 의사결정을 거쳤나요?
잠깐 고민했던 부분은 firebase에 상품을 저장할때 카테고리 별로 나눌지, 통으로 저장할지였다. 데이터가 많지 않기에 오히려 관리하기 편하도록 통으로 저장하는 쪽을 택했다.
상품 데이터를 파이어스토어에 저장할 때 카테고리별로 별도의 컬렉션을 만들지, 아니면 한 컬렉션(products
)에 모두 저장하고 category
필드로 필터링할지 결정하는 것은 데이터 관리와 조회 성능에 영향을 미친다.
category
필드를 기준으로 필터링하여 수행할 수 있으므로, 복잡한 쿼리 로직 없이도 원하는 데이터를 효율적으로 조회.홈 화면에서는 카테고리별로 4개의 상품만 보여주고, 더보기 버튼을 통해 카테고리 페이지로 이동해서 무한스크롤을 적용해야 했다.
첫 화면에서 사용할 useQuery와 카테고리 페이지에서 사용할 useInfiniteQuery를 구분해서 따로 만들었다.
그리고 무한스크롤에서 중요한 점은 기준점을 어디로 삼고 코드상의 위치를 어디에 두는지 중요한것같다. 카테고리 페이지로 이동하자마자 스크롤을 내린것처럼 네트워크 요청이 한번에 전달되거나, 스크롤을 한번 내렸는데 계속 내린것처럼 모든 상품을 한번에 가져오는 문제가 있었다.
ref
요소가 어떻게 배치되고, 뷰포트에 어떻게 드러나는지에 따라 네트워크 요청의 동작이 달라지기에 중요하다.
추가로 ref props의 div가 data를 map 메서드로 보여주고 있는 태그 내부에 있어야 작동한다.
작동 안되는 예시
<div>
<div>
{data?.pages &&
data?.pages.flatMap((page) =>
page.products.map((product) => (
<ProductCard data={product} key={product.id} />
))
)}
</div>
<div ref={hasNextPage ? ref : undefined} />
</div>
'react-intersection-observer'
를 이용해서 ref 요소를 지정했는데 잘못됐던 코드와 수정한 코드이다.
import { useInView } from 'react-intersection-observer'
<div>
{data?.pages &&
data?.pages.flatMap((page) =>
page.products.map((product) => (
<ProductCard data={product} key={product.id} />
))
)}
{isFetchingNextPage ? (
<p>Loading more...</p>
) : (
hasNextPage && <div ref={ref} />
)}
</div>
ref
가 항상 활성화되어 있기 때문. ref
요소가 항상 뷰포트에 보이게 배치되어 있다면, 스크롤이 이 요소를 지날 때마다 계속해서 데이터 로드된다. inView
상태가 true
가 되고, 이 상태를 활용하여 추가 데이터를 요청하기 때문이다.
수정한 코드
<div>
{data?.pages &&
data?.pages.flatMap((page) =>
page.products.map((product) => (
<ProductCard data={product} key={product.id} />
))
)}
<div ref={hasNextPage ? ref : undefined} />
{isFetchingNextPage && <p>Loading more...</p>}
</div>
</div>
노란 부분을 보면 Last visibel이 없고 pages: Array(17)은 마지막에 해당하는 데이터인데, 화면을 누르거나 하는 동작을 하면 처음부터 다시 요청을 하고 있음
요청이 계속 쌓인걸 볼수있다.
왜그럴까? 이유를 찾아보니 페이지 내에서 동작을 할때는 중복으로 요청이 안되는데 탭을 옮겼다가 다시 누르거나, 개발자 도구에서 콘솔이나 네트워크 탭을 누르면 처음부터 데이터를 불러오길래 코드상 문제가 아니라 다른 문제인 것 같아서 찾아보니 리액트 쿼리의 기본 기능이었다. 데이터를 새로 요청할 필요가 없어서 refetchOnWindowFocus: false
를 추가해서 방지했다.
export const useQueryProducts = () => {
return useInfiniteQuery({
// ... 중략
refetchOnWindowFocus: false,
})
}