Skip to content

๐Ÿงช ์ˆ˜๋งŽ์€ ๊ทธ๋ž˜ํ”„ ๋ฐ์ดํ„ฐ ์š”์ฒญ์„ ์–ด๋–ป๊ฒŒ ์ค„์ผ๊นŒ

baegyeong edited this page Dec 2, 2024 · 1 revision
๋ถ„์•ผ ์ž‘์„ฑ์ž ์ž‘์„ฑ์ผ
FE ์กฐ๋ฐฐ๊ฒฝ 24๋…„ 12์›” 03์ผ

๋„ˆ๋ฌด ๋งŽ์€ ์š”์ฒญ

๊ทธ๋ž˜ํ”„๋ฅผ ์‚ด์ง๋งŒ ์›€์ง์—ฌ๋„, ๋ฐ์ดํ„ฐ๊ฐ€ ์™€๋ฅด๋ฅด ์š”์ฒญ๋๋‹ค. infinitescroll


๊ทธ๋ž˜ํ”„๋ฅผ ์™ผ์ชฝ์œผ๋กœ ์Šคํฌ๋กค ํ–ˆ์„ ๋•Œ ๋ฐ์ดํ„ฐ๋ฅผ ์ƒˆ๋กœ ํŠธ๋ฆฌ๊ฑฐํ•˜๋Š” ๊ฒƒ์€ ์•„๋ž˜์˜ ์กฐ๊ฑด ๋ฟ์ด๋‹ค.

if (logicalRange?.from < 10 && hasNextPage) {
  fetchNextPage();
}
  • ํ˜„์žฌ ๋ณด์ด๋Š” ์˜์—ญ์˜ ์‹œ์ž‘์ ์˜ ์œ„์น˜๊ฐ€ 10๋ณด๋‹ค ์ž‘๊ณ , ๋‹ค์Œ ํŽ˜์ด์ง€๊ฐ€ ์žˆ๋‹ค๋ฉด fetchNextPage๋ฅผ ์‹คํ–‰ํ•œ๋‹ค.

์ฐจํŠธ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†๋Š” ๊ตฌ๊ฐ„์€ logicalRange.from์ด ์ „๋ถ€ 10๋ณด๋‹ค ์ž‘๋‹ค.

๋”ฐ๋ผ์„œ if๋ฌธ์ด ๊ณ„์† true๊ฐ€ ๋˜์–ด ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜จ๋‹ค.


์šฐ์„ , ์™œ ์ค‘๋ณต ํ˜ธ์ถœ์„ ํ• ๊นŒ?

tanstack query์˜ ํŠน์ง•์œผ๋กœ๋Š” queryKey๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ค‘๋ณต ์š”์ฒญ์„ ๋ฐฉ์ง€ํ•˜๋Š” ๊ฒƒ์ธ๋ฐ, ์™œ ์ค‘๋ณต ์š”์ฒญ์ด ๋ฐœ์ƒํ• ๊นŒ?

๋‚ด ์ƒ๊ฐ์€

  1. useInfiniteQuery๋Š” pageParam์„ ์บ์‹ฑํ•˜๊ณ  ์žˆ์ง€ ์•Š๊ฑฐ๋‚˜
  2. ์š”์ฒญ์ด ์™„๋ฃŒ๋˜์ง€ ์•Š์€ ์ƒํƒœ์—์„œ ์ถ”๊ฐ€์ ์œผ๋กœ ์š”์ฒญ์„ ํ•ด์„œ ์ค‘๋ณต์ฒ˜๋Ÿผ ๋ณด์ด๋Š” ๊ฒƒ

์ด ์•„๋‹๊นŒ ์‹ถ๋‹ค. ์‹ค์ œ๋กœ tanstack query์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•  ๋•Œ๋Š” ์ค‘๋ณต์„ ํ—ˆ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค.


  1. pageParam ์บ์‹ฑ ์—ฌ๋ถ€

    1๋ฒˆ ๊ฐ€์„ค์€ ์‹ค์ œ๋กœ ์‹œ๊ฐ„์ฐจ๋ฅผ ๋‘๊ณ  ํ•ด๋‹น ๊ตฌ๊ฐ„์„ ์š”์ฒญํ•ด๋„ ์ค‘๋ณต ์š”์ฒญ์ด ๊ฐ€๋Š”์ง€ ํŒ๋‹จํ•ด๋ณด๋ฉด ์•Œ ์ˆ˜ ์žˆ๊ฒ ๋‹ค.

    infinitescroll_duplicate

    lastStartTime์„ ์ง‘์ค‘ํ•ด์„œ ๋ณด๋ฉด ๋œ๋‹ค.

    ์ฒ˜์Œ์— ๋ฐ์ดํ„ฐ๋ฅผ ๊ณ„์† ์š”์ฒญํ•  ๋• ์‚ฌ์šฉ์ž์˜ ์Šคํฌ๋กค์ด ๋„ˆ๋ฌด ๋น ๋ฅธ ๋‚˜๋จธ์ง€ 7์›” 3์ผ์ž์˜ ์š”์ฒญ์ด 3๋ฒˆ ๊ฐ„๋‹ค. ๋‹ค์‹œ ์˜ค๋ฅธ์ชฝ์œผ๋กœ ๋Œ์•„์™€ ๋ˆ„์ ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณผ ๋•Œ๋Š” ์ด๋ฏธ ๋ถˆ๋Ÿฌ์™”๋˜ ๋ฐ์ดํ„ฐ์ด๊ธฐ ๋•Œ๋ฌธ์— 7์›” 3์ผ์„ ๋‹ค์‹œ ๋ถ€๋ฅด์ง€ ์•Š๋Š”๋‹ค.

    ์ฆ‰, pageParam์„ ์บ์‹ฑํ•˜์ง€ ์•Š๋Š”๋‹ค๋ผ๋Š” ๊ฒƒ์„ ๋‹จ์ •์ง“๊ธฐ๋ณด๋‹ค๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์บ์‹ฑํ•ด๋‘๋Š” ๊ฒƒ์€ ๋งž๋‹ค.

  2. ์š”์ฒญ์ด ์™„๋ฃŒ๋˜์ง€ ์•Š์€ ์ƒํƒœ์—์„œ ์ถ”๊ฐ€์ ์œผ๋กœ ์š”์ฒญ์„ ํ•ด์„œ ์ค‘๋ณต์ฒ˜๋Ÿผ ๋ณด์ด๋Š” ๊ฒƒ

    ์ข€ ๋” ์ž์„ธํžˆ ์‚ดํŽด๋ณด๊ธฐ ์œ„ํ•ด network๋ฅผ slow 4G๋กœ ์„ค์ •ํ•œ ํ›„ ํ™•์ธํ•ด๋ดค๋‹ค.

    ํ•˜๋‚˜์˜ ์š”์ฒญ์ด pending ์ƒํƒœ์ผ ๋•Œ ์Šคํฌ๋กค์„ ๊ณ„์† ํ•œ๋‹ค๋ฉด ๋™์ผํ•œ api๊ฐ€ ์ค‘๋ณต๋˜์–ด ์š”์ฒญ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

    same request


์ฆ‰, ์‚ฌ์šฉ์ž์˜ ์Šคํฌ๋กค ์†๋„๊ฐ€ ๋ฐ์ดํ„ฐ ํŒจ์นญ ์†๋„๋ณด๋‹ค ์›”๋“ฑํžˆ ๋นจ๋ผ์„œ ๋‹ค์ˆ˜์˜ ์š”์ฒญ์ด ์ƒ๊ธด ๊ฒƒ์ด๋‹ค.


logicalRange์˜ ๋ฒ”์œ„ ์ œํ•œ

๊ทธ๋ ‡๋‹ค๋ฉด logicalRange.from์˜ ๋ฒ”์œ„๋ฅผ ์ œํ•œํ•ด๋ณด๋Š” ๊ฑด ์–ด๋–จ๊นŒ?

logicalRange?.from > 10 && logicalRange?.from < 11 

์ด ๋ฐฉ๋ฒ•์€ ์œ„ํ—˜ํ•˜๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ์–ผ๋งˆ๋‚˜ ๋นจ๋ฆฌ ๊ทธ๋ž˜ํ”„๋ฅผ ์Šคํฌ๋กค ํ•˜๋Š๋ƒ์— ๋”ฐ๋ผ from ๊ฐ’์ด 10~11 ๋ฒ”์œ„์— ์žˆ์„์ˆ˜๋„ ์žˆ๊ณ , ์—†์„ ์ˆ˜๋„ ์žˆ๋‹ค!

์šด์ด ์ข‹๋‹ค๋ฉด ์ € ๋ฒ”์œ„ ์•ˆ์— ๋“ค์–ด๊ฐ€์„œ ๋ฐ์ดํ„ฐ๋ฅผ ํŒจ์นญํ•˜๊ฒ ์ง€๋งŒ, ๊ทธ๋ ‡์ง€ ์•Š์€ ์•„๋ž˜์˜ ๊ฒฝ์šฐ๋„ ์žˆ๋‹ค. ๐Ÿ˜‚

image

์ด๋ ‡๊ฒŒ ๋˜๋ฉด ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค์ง€ ๋ชปํ•œ๋‹ค.

๊ทธ๋ ‡๋‹ค๊ณ  ๋ฒ”์œ„๋ฅผ ๋„“๊ฒŒ ์ง€์ •ํ•œ๋‹ค๋ฉด.. ๋˜ ๋‹ค์‹œ ์š”์ฒญ์ด ๋งŽ์ด ๋ณด๋‚ด์งˆ ๊ฒƒ์ด๋‹ค.


์“ฐ๋กœํ‹€๋ง์„ ์ ์šฉํ•ด๋ณธ๋‹ค๋ฉด?

์งง์€ ์‹œ๊ฐ„๋™์•ˆ ์ด๋ฒคํŠธ๋“ค์ด ์—ฐ์†ํ•ด์„œ ๋ฐœ์ƒํ•˜๋ฏ€๋กœ, delay ๋‹จ์œ„๋กœ ๊ทธ๋ฃนํ™”ํ•˜์—ฌ ํ•œ๋ฒˆ๋งŒ ์—…๋ฐ์ดํŠธ๊ฐ€ ๋˜๋„๋ก ํ•ด๋ณด์ž.

์Šคํฌ๋กค์„ ํ†ตํ•ด ๊ณ„์†ํ•ด์„œ ์š”์ฒญ์„ ํ•œ๋‹ค๋ฉด, isFetching ์ƒํƒœ๋„ ๊ณ„์†ํ•ด์„œ ๋ณ€ํ™”ํ•  ๊ฒƒ์ด๋‹ค.

๋”ฐ๋ผ์„œ 700ms ๊ฐ„๊ฒฉ์œผ๋กœ ์—…๋ฐ์ดํŠธ๋˜๊ฒŒ ํ•˜์—ฌ, ํ•ด๋‹น ๊ฐ’์ด false์ผ ๋•Œ๋งŒ ๋ฐ์ดํ„ฐ๋ฅผ ํŠธ๋ฆฌ๊ฑฐํ•œ๋‹ค.

๋น„์œ ํ•˜์ž๋ฉด โ€˜์š”์ฒญ ์ค‘โ€™ ์ƒํƒœ๋ฅผ ์ค‘๊ฐ„์ค‘๊ฐ„ ๋ธ”๋กœํ‚น ํ•˜๋Š” ๋Š๋‚Œ์ด๋‹ค.

// isFetching์˜ ๋ณ€ํ™”๋ฅผ ์“ฐ๋กœํ‹€๋ง
// 700ms ๊ฐ„๊ฒฉ์œผ๋กœ๋งŒ throttledIsFetcing ๊ฐ’์„ ์—…๋ฐ์ดํŠธ
const throttledIsFetching = useThrottle(isFetching, 700);

const fetchGraphData = useCallback((logicalRange) => {
 // throttledIsFetching์ด false์ผ ๋•Œ์˜ ์กฐ๊ฑด ์ถ”๊ฐ€
  if (logicalRange?.from < 10 && hasNextPage && !throttledIsFetching) {
      fetchNextPage();
  }
}, [hasNextPage, throttledIsFetching, fetchNextPage]);

useEffect(() => {
  const chartInstance = chart.current;
  if (!chartInstance) return;

  const timeScale = chartInstance.timeScale();

  timeScale.subscribeVisibleLogicalRangeChange(fetchGraphData);

  return () => {
    timeScale.unsubscribeVisibleLogicalRangeChange(fetchGraphData);
  };
}, [chart, fetchGrahData]);

๊ทธ ๊ฒฐ๊ณผ, ์š”์ฒญ์€ ์ค„์—ˆ๋‹ค. ํ•˜์ง€๋งŒ ์—ฌ์ „ํžˆ ์‚ฌ์šฉ์ž๊ฐ€ ๋น ๋ฅด๊ฒŒ ์Šคํฌ๋กคํ•œ๋‹ค๋ฉด, ๋™์ผํ•œ lastStartTime ์œผ๋กœ ์ค‘๋ณต ์š”์ฒญ์ด ๋ฐœ์ƒํ•œ๋‹ค.

image

๊ทธ ์ด์œ ๋Š” ๋ฌด์—‡์ผ๊นŒ?


๋งŒ์•ฝ throttledIsFetching์ด false๋กœ ์—…๋ฐ์ดํŠธ ๋˜๊ธฐ ์ „์— if๋ฌธ์˜ ์กฐ๊ฑด์ด ์ถฉ์กฑ๋˜๋Š” ๊ฒฝ์šฐ๋ฅผ ์ƒ๊ฐํ•ด๋ณด์ž.

  1. ๋ฐ์ดํ„ฐ๋ฅผ ํ˜„์žฌ ์š”์ฒญ ์ค‘์ด๋ผ์„œ throttledIsFetching์ด true ๋ผ๊ณ  ๊ฐ€์ •ํ•˜์ž.
  2. ์ด ์ƒํ™ฉ์—์„œ ์Šคํฌ๋กค์„ ๊ณ„์†ํ•ด์„œ 700ms ์•ˆ์— ๋˜ ์š”์ฒญ์„ ํ•œ๋‹ค๋ฉด, if๋ฌธ์€ ์•„์ง ์ฐธ์ธ ์ƒํƒœ์ด๊ธฐ ๋•Œ๋ฌธ์— fetchNextPage๊ฐ€ ์‹คํ–‰๋˜๊ณ  ๋งŒ๋‹ค. ์ฆ‰, ์Šค๋กœํ‹€๋ง์„ ์ ์šฉํ•œ ์ƒํƒœ๊ฐ€ fetchNextPage๋ฅผ ์™„์ „ํžˆ ์ œ์–ดํ•˜์ง€ ๋ชปํ•œ๋‹ค.

๋˜ํ•œ ์ผ์ • ์‹œ๊ฐ„ ๊ฐ„๊ฒฉ์œผ๋กœ ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธํ•  ๋ฟ, fetchNextPage์˜ ์™„๋ฃŒ์—ฌ๋ถ€์™€๋Š” ๋ฌด๊ด€ํ•˜๋‹ค. ๋„คํŠธ์›Œํฌ ์ƒํ™ฉ์— ๋”ฐ๋ผ์„œ ์•„์ง ์š”์ฒญ์ด ์™„๋ฃŒ๋˜์ง€ ์•Š์•˜์Œ์—๋„ throttledIsFetching์ด false๊ฐ€ ๋  ์ˆ˜ ์žˆ๋‹ค. ๋”ฐ๋ผ์„œ ์„œ๋ฒ„์™€ ํด๋ผ์ด์–ธํŠธ ๊ฐ„์˜ ๋ฐ์ดํ„ฐ ๋™๊ธฐํ™” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ๋‹ค.

๋งŒ์•ฝ ์‚ฌ์šฉ์ž๊ฐ€ ๋” ๋น ๋ฅด๊ฒŒ ๋ฐ์ดํ„ฐ๋ฅผ ์›ํ•˜๋Š” ๊ฒฝ์šฐ์—๋„, ์›์น˜ ์•Š์€ ์ง€์—ฐ์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค.

๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— ๋” ์ •ํ™•ํ•œ ์ค‘๋ณต ํ˜ธ์ถœ์„ ๋ง‰์„ ๋ฐฉ๋ฒ•์ด ํ•„์š”ํ•˜๋‹ค.


useRef๋ฅผ ์ด์šฉํ•˜์—ฌ ์ค‘๋ณตํ˜ธ์ถœ ๋ง‰๊ธฐ

useInfiniteQuery์˜ isFetching์˜ ๊ฒฝ์šฐ์—”, ์š”์ฒญ์ด ํ•œ ๊ฐœ๋งŒ ์ง„ํ–‰๋˜๋Š” ๊ฒƒ์„ ๋ณด์žฅํ•˜์ง€ ์•Š๋Š”๋‹ค. ๊ณ„์† ์Šคํฌ๋กค๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์š”์ฒญํ•œ๋‹ค๋ฉด, ์ด์ „ ์š”์ฒญ์ด ๋๋‚˜๊ธฐ๋„ ์ „์— isFetching์˜ ์ƒํƒœ๊ฐ€ ๊ณ„์†ํ•ด์„œ ๋ณ€ํ™”ํ•œ๋‹ค.

๋”ฐ๋ผ์„œ ๋” ๋ช…ํ™•ํžˆ ๋ฐ์ดํ„ฐ ํŒจ์นญ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด์„œ, ๋ณ„๋„์˜ ์ƒํƒœ๋ฅผ ๋„์ž…ํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค.

useRef๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ƒํƒœ๋ฅผ ์ €์žฅํ•œ๋‹ค๋ฉด ํ•ด๋‹น ์ƒํƒœ์— ๋Œ€ํ•œ ๋ฆฌ๋ Œ๋”๋ง์€ ๋ง‰์œผ๋ฉฐ API์— ๋Œ€ํ•œ ์ค‘๋ณตํ˜ธ์ถœ๋„ ๋ง‰์„ ์ˆ˜ ์žˆ์„ ๊ฒƒ์ด๋‹ค.

isPending ์ƒํƒœ๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ๋‹ค๊ณ  ํ•ด์„œ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ฆฌ๋ Œ๋”๋ง์ด ๋  ํ•„์š”๊ฐ€ ์—†๊ธฐ ๋•Œ๋ฌธ์— useRef๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ด€๋ฆฌํ•œ๋‹ค.


// pending ์ƒํƒœ ์ €์žฅ
const isPending = useRef(false);

// ๊ทธ๋ž˜ํ”„์˜ ๊ฐ€์‹œ ๋ฒ”์œ„๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฉด ์‹คํ–‰๋˜๋Š” ํ•จ์ˆ˜. ์ฆ‰ ์Šคํฌ๋กคํ•˜๋ฉด ์‹คํ–‰๋จ
const fetchGraphData = useCallback(
  async (logicalRange: LogicalRange | null) => {
    // ์š”์ฒญ์ด ์ง„ํ–‰ ์ค‘์ด๋ผ๋ฉด ํ•จ์ˆ˜ ์ข…๋ฃŒ
    if (isPending.current) return;
    
    // ๊ทธ๋ ‡์ง€ ์•Š๋‹ค๋ฉด ์š”์ฒญ์ด ์ง„ํ–‰์ค‘
    isPending.current = true;

    if (logicalRange && logicalRange.from < 10 && hasNextPage) {
      await fetchNextPage();
    }
	
    // ์š”์ฒญ์ด ๋๋‚˜๋ฉด false๋กœ ์„ค์ •
    isPending.current = false;
  },
  [hasNextPage, isFetching, fetchNextPage],
);

// ์ด์ „๊ณผ ๋™์ผ
useEffect(() => {
  const chartInstance = chart.current;
  if (!chartInstance) return;

  const timeScale = chartInstance.timeScale();
  timeScale.subscribeVisibleLogicalRangeChange(fetchGraphData);

  return () => {
    timeScale.unsubscribeVisibleLogicalRangeChange(fetchGraphData);
  };
}, [chart, fetchGraphData]);
  • fetchGraphData๋Š” ๊ทธ๋ž˜ํ”„์˜ ๊ฐ€์‹œ ๋ฒ”์œ„๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฉด ํ˜ธ์ถœ๋˜๋Š”๋ฐ, ์ด๋•Œ isPending ํ”Œ๋ž˜๊ทธ๋ฅผ ํ†ตํ•ด ์ค‘๋ณต ์š”์ฒญ์„ ๋ฐฉ์ง€ํ•œ๋‹ค.
  • async/await๋กœ fetchNextPage ํ˜ธ์ถœ ์‹œ ํ•ด๋‹น ์ž‘์—…์ด ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฐ๋‹ค. api ์‘๋‹ต ์™„๋ฃŒ ์ „์— isPending.current๊ฐ€ false๋กœ ์‹คํ–‰ํ•˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•œ๋‹ค.

๋ ˆํผ๋Ÿฐ์Šค

Debounce(๋””๋ฐ”์šด์Šค) - JavaScript์—์„œ ํ•จ์ˆ˜๋ฅผ ์ง€์—ฐ์‹œํ‚ค๋Š” ๋ฐฉ๋ฒ• (JS ES6 ์˜ˆ์ œ)

๋ฆฌ์•กํŠธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ API ์ค‘๋ณต ํ˜ธ์ถœ์„ ์ •ํ™•ํžˆ ๋ง‰๋Š” ๋ฐฉ๋ฒ•

๐Ÿœ ํŒ€ ๊ฐœ๋ฏธ

๐Ÿ›๏ธ ํŒ€ ๋ฌธํ™”

๊ฐœ๋ฐœ ์œ„ํ‚ค

FE

BE

Infra

๐Ÿ—ฃ๏ธ ๋ฐœํ‘œ

๐Ÿ“š ํšŒ์˜๋ก

๐Ÿ”ด ์ธํ„ฐ๋ฏธ์…˜
๐ŸŸ  1์ฃผ์ฐจ
๐ŸŸก 2์ฃผ์ฐจ
๐ŸŸข 3์ฃผ์ฐจ
๐Ÿ”ต 4์ฃผ์ฐจ
๐ŸŸฃ 5์ฃผ์ฐจ
๐ŸŸค 6์ฃผ์ฐจ

๐Ÿ’ญ ํšŒ๊ณ 

๐Ÿง‘โ€๐Ÿคโ€๐Ÿง‘ ๋ฉ˜ํ† ๋ง

Clone this wiki locally