๋ฐ˜์‘ํ˜•

๋ฌธ์ œ ์›์ธ


๋ Œ๋”๋ง ๋ฐ์ดํ„ฐ์˜ ๋งˆ์ง€๋ง‰ ์ธ๋ฑ์Šค ์œ„์น˜์— IO(Intersection Observer) ๊ด€์ฐฐ ์š”์†Œ๋ฅผ ํ‘œ์‹œํ•˜๋Š” ๋ฐฉ์‹์€ ๋ฌดํ•œ ๋ Œ๋”๋ง ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค.

 

๋ฐ์ดํ„ฐ๊ฐ€ 1~2๊ฐœ ๋ฐ–์— ์—†์–ด์„œ ๊ด€์ฐฐ ์š”์†Œ๊ฐ€ ํ•ญ์ƒ ํ™”๋ฉด์— ๋“ค์–ด์˜จ๋‹ค๋ฉด โžŠfetchNextPage ์‹คํ–‰(React Query ํ›…์ด ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜) → โž‹๊ธฐ์กด ๋ฐ์ดํ„ฐ ๋ฐ˜ํ™˜ → โžŒ๋ Œ๋” → โžIO ํ™”๋ฉด์— ๋“ค์–ด์˜ด → โžŠfetchNextPage ์‹คํ–‰ → ๋ฐ˜๋ณต…

 

์œ„ ๊ณผ์ •์„ ๊ณ„์† ๋ฐ˜๋ณตํ•˜๋ฉด์„œ ๋ฌดํ•œ ๋ Œ๋”๋งํ•˜๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค. Query Key๊ฐ€ ๋ฐ”๋€Œ์ง€ ์•Š์•„์„œ ๋ฐ์ดํ„ฐ fetch ์—†์ด ์บ์‹ฑํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜์ง€๋งŒ ๋ฌดํ•œ ๋ Œ๋”๋ง ๋•Œ๋ฌธ์— ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์ •์ƒ์ ์œผ๋กœ ์ž‘๋™ํ•˜์ง€ ์•Š๋Š”๋‹ค. ๋ฐ์ดํ„ฐ๊ฐ€ ์•„์˜ˆ ์—†๋‹ค๋ฉด IO ๊ด€์ฐฐ ๋Œ€์ƒ ์š”์†Œ๋„ ๋ Œ๋”๋ง ํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ ๋ฌธ์ œ๊ฐ€ ์—†๋Š”๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ด๊ธฐ ๋•Œ๋ฌธ์— ์ฃผ์˜ํ•ด์•ผ๋  ๋ฒ„๊ทธ.

// queryKey ๋ฐ queryFn ์ƒ๋žต
const { data, fetchNextPage } = useInfiniteQuery(queryKey, queryFn);

const { setObservationTarget } = useIntersectionObserver({
  onIntersect: ([{ isIntersecting }]) => isIntersecting && fetchNextPage(),
});

const renderData = data?.pages.flatMap(({ data }) => data.orders);

return (
  <div>
    {renderData?.map((order, i, { length }) => (
      <Fragment key={order.order_id}>
        <OrderListItem data={order} />
        {i === length - 1 && <div ref={setObservationTarget} />} {/* ๋ฌธ์ œ์˜ ์ฝ”๋“œ */}
      </Fragment>
    ))}
  </div>
);

 

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•


์œ„ ๋ฌธ์ œ๋Š” IO ๊ด€์ฐฐ ์š”์†Œ ํ‘œ์‹œ ์กฐ๊ฑด์„ ์ง€์ •ํ•  ๋•Œ useInfiniteQuery ํ›…์ด ๋ฐ˜ํ™˜ํ•˜๋Š” hasNextPage๋ฅผ ์ด์šฉํ•˜๋ฉด ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค. hasNextPage ๊ฐ’์€ getNextPageParam(๋‹ค์Œ ์š”์ฒญ์‹œ ์‚ฌ์šฉํ•  pageParam์„ ์ง€์ •ํ•˜๋Š” ํ•จ์ˆ˜)์ด ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฐ’์— ๋”ฐ๋ผ ๋‹ฌ๋ผ์ง„๋‹ค. getNextPageParam ํ•จ์ˆ˜๊ฐ€ truthy ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๋ฉด hasNextPage๋Š” true๊ฐ€ ๋˜๊ณ  undefined๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉด false๊ฐ€ ๋œ๋‹ค.

const { data, fetchNextPage, isFetching, hasNextPage } = useInfiniteQuery(
  queryKey, // ์ƒ๋žต
  queryFn, // ์ƒ๋žต
  { getNextPageParam: (lastPage) => lastPage.data.cursor ?? undefined },
  // cursor๋Š” ๋‹ค์Œ ์š”์ฒญ์‹œ pageParam์œผ๋กœ ์‚ฌ์šฉ๋จ. ๊ฐ’์€ 'ISO format string' | null
);

// ...

return (
  <div>
    {renderData?.map((order) => (
      <Fragment key={order.order_id}>
        <OrderListItem data={order} />
        {hasNextPage && <div ref={setObservationTarget} />} {/* ์ˆ˜์ •ํ•œ ์ฝ”๋“œ */}
      </Fragment>
    ))}
  </div>
);

 


๊ธ€ ์ˆ˜์ •์‚ฌํ•ญ์€ ๋…ธ์…˜ ํŽ˜์ด์ง€์— ๊ฐ€์žฅ ๋น ๋ฅด๊ฒŒ ๋ฐ˜์˜๋ฉ๋‹ˆ๋‹ค. ๋งํฌ๋ฅผ ์ฐธ๊ณ ํ•ด ์ฃผ์„ธ์š”
๋ฐ˜์‘ํ˜•