[Virtualized List] #2. Virtualized List์˜ ๊ธฐ๋Šฅ

์ด๋ฒˆ ์ฑ•ํ„ฐ์—์„œ๋Š” ๊ฐ€์ƒ ๋ฆฌ์ŠคํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์„œ๋น„์Šค์—์„œ ํ•„์š”๋กœ ํ•˜๋Š” ๊ธฐ๋Šฅ๋“ค์„ ๋ชจ๋‘ ๋ฆฌ์ŠคํŒ… ํ•ด๋ณด๊ณ , ๋ช‡๋ช‡์˜ ๊ธฐ๋Šฅ์ด ์–ด๋–ป๊ฒŒ ๊ตฌํ˜„๋˜๋Š”์ง€ ์™œ ์–ด๋ ค์šด ์ง€ ์•Œ์•„๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

Major ๊ธฐ๋Šฅ

  • ์œˆ๋„์šฐ ์Šคํฌ๋กค์„ ๋ฆฌ์ŠคํŠธ ์Šคํฌ๋กค์— ์ ์šฉ
  • ํŽ˜์ด์ง€ ์žฌ๋ฐฉ๋ฌธ ์‹œ ์Šคํฌ๋กค ์œ„์น˜ ์œ ์ง€ ๐Ÿ”ฅ
  • ์•„์ดํ…œ์˜ ๋†’์ด๊ฐ€ ์ž๋™์œผ๋กœ ์กฐ์ ˆ
  • ํ™”๋ฉด์— ๋ณด์ด๋Š” ์•„์ดํ…œ๋งŒ ๋ Œ๋”๋ง
  • ์—ด์˜ ์ˆ˜๋ฅผ ๋ฐ˜์‘ํ˜• ์œผ๋กœ ์กฐ์ •
  • ์–‘๋ฐฉํ–ฅ ๋ฌดํ•œ ๋ฐ์ดํ„ฐ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ๊ณผ ์—ฐ๊ฒฐ ๐Ÿ”ฅ

๊ตฌํ˜„ํ•˜๊ธฐ ๊นŒ๋‹ค๋กœ์šด ๋ถ€๋ถ„์ด ์žˆ๋Š” ๊ธฐ๋Šฅ์€ ๐Ÿ”ฅ ๋กœ ํ‘œ์‹œํ–ˆ์Šต๋‹ˆ๋‹ค.


์œˆ๋„์šฐ ์Šคํฌ๋กค์„ ๋ฆฌ์ŠคํŠธ ์Šคํฌ๋กค์— ์ ์šฉ

์•„๋ž˜์˜ ์˜์ƒ์„ ๋ณด๋ฉด ์ƒํ™ฉ์— ๋Œ€ํ•œ ์ดํ•ด๊ฐ€ ์‰ฝ์Šต๋‹ˆ๋‹ค.

(์™ผ) ๋ฆฌ์ŠคํŠธ ๋‚ด๋ถ€ ์Šคํฌ๋กค / (์˜ค) ์ „์ฒด ํŽ˜์ด์ง€ ์Šคํฌ๋กค(์œˆ๋„์šฐ ์Šคํฌ๋กค)

 

๋ฆฌ์ŠคํŠธ ๋‚ด๋ถ€ ์Šคํฌ๋กค์˜ ๊ฒฝ์šฐ list-container ๋ผ๋Š” div์˜ height๊ฐ€ ์ •ํ•ด์ ธ์žˆ๊ณ  overflow-y๊ฐ€ scroll์ž…๋‹ˆ๋‹ค. ์ฆ‰, ํŽ˜์ด์ง€์˜ ๊ตฌ์„ฑ์ด (ํ—ค๋”, ๋ฆฌ์ŠคํŠธ, ํ‘ธํ„ฐ)๋กœ ๋˜์–ด์žˆ์„ ๋•Œ ๋ฆฌ์ŠคํŠธ์—์„œ๋งŒ ์Šคํฌ๋กค์ด ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. (virtuoso ์˜ˆ์‹œ)

ํ•˜์ง€๋งŒ ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ ๊ฐ€์ƒ ๋ฆฌ์ŠคํŠธ๋ฅผ ๊ตฌํ˜„ํ•  ๋•Œ ์ „์ฒด ํŽ˜์ด์ง€ ์Šคํฌ๋กค(์œˆ๋„์šฐ ์Šคํฌ๋กค) ์˜ ์˜ˆ์‹œ์™€ ๊ฐ™์ด ๋งˆ์šฐ์Šค๊ฐ€ ํ—ค๋”์— ์žˆ๋˜ ํ‘ธํ„ฐ์— ์žˆ๋˜ ์Šคํฌ๋กค์ด ํ•˜๋‚˜์ฒ˜๋Ÿผ ๋™์ž‘ ํ•˜๊ธฐ๋ฅผ ๋ฐ”๋ž๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์œˆ๋„์šฐ ์Šคํฌ๋กค ์‹œ ์ด๋ฒคํŠธ๋ฅผ ๋ฐ›์•„์„œ ๋ฆฌ์ŠคํŠธ ๋‚ด๋ถ€๊ฐ€ ์›€์ง์ผ ์ˆ˜ ์žˆ๋„๋ก ๊ตฌํ˜„ํ•ด ์ฃผ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

react-window ๋ผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ๊ฒฝ์šฐ ์ด๋Ÿฌํ•œ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด 3rd party ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ธ react-window-scroller ๋ฅผ ์—ฐ๊ฒฐํ•˜์—ฌ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์•„๋ž˜์˜ ์ฝ”๋“œ๋Š” react-window-scroller ์˜ ์ฝ”๋“œ ์ค‘ ํ•„์š”ํ•œ ๋ถ€๋ถ„๋งŒ ์ธ์šฉํ•ด์˜จ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

useEffect(() => {
    const handleWindowScroll = throttle(() => {
      const { offsetTop = 0, offsetLeft = 0 } = outerRef.current || {}
      const scrollTop = getScrollPosition('y') - offsetTop
      const scrollLeft = getScrollPosition('x') - offsetLeft
			// ์ด๋™ํ•˜๊ณ ์ž ํ•˜๋Š” ์œ„์น˜๋กœ ๋ฆฌ์ŠคํŠธ ์ปดํฌ๋„ŒํŠธ scroll ์ด๋™
      if (isGrid) ref.current && ref.current.scrollTo({ scrollLeft, scrollTop })
      if (!isGrid) ref.current && ref.current.scrollTo(scrollTop)
    }, throttleTime)

		 // window ์Šคํฌ๋กค ๋ฐœ์ƒ ์‹œ handleWindowScroll ์‹คํ–‰
    window.addEventListener('scroll', handleWindowScroll)
    return () => {
      handleWindowScroll.cancel()
      window.removeEventListener('scroll', handleWindowScroll)
    }
  }, [isGrid])

์ด๋ ‡๊ฒŒ ์œˆ๋„์šฐ ์Šคํฌ๋กค ๋™์ž‘ ์‹œ ์Šคํฌ๋กค ์œ„์น˜๋ฅผ ๋ฐ›์•„์„œ ๋ฆฌ์ŠคํŠธ์˜ ์Šคํฌ๋กค์„ ์กฐ์ •ํ•˜๋Š” ๋กœ์ง์„ ์ถ”๊ฐ€ํ•ด์ฃผ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์ด ๊ธฐ๋Šฅ์€ ์–ด๋–ค ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์—์„œ๋Š” props ํ•˜๋‚˜๋กœ, ์–ด๋–ค ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์—์„œ๋Š” 3rd party ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์—ฐ๋™ํ•˜์—ฌ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ, ์ด ๊ธฐ๋Šฅ ์ž์ฒด๊ฐ€ ๊ตฌํ˜„์ด ์–ด๋ ค์šด ๊ฒƒ์ด ์•„๋‹ˆ๊ณ  ๋‹ค๋ฅธ ๊ธฐ๋Šฅ ๊ณผ์˜ ์—ฐ๋™์ด ์–ด๋ ต์Šต๋‹ˆ๋‹ค.

 

ํŽ˜์ด์ง€ ์žฌ๋ฐฉ๋ฌธ ์‹œ ์Šคํฌ๋กค ์œ„์น˜ ์œ ์ง€ ๐Ÿ”ฅ

์ด ๊ธฐ๋Šฅ์€ ์ƒ๊ฐ๋ณด๋‹ค ๊ณ ๋ คํ•  ๊ฒŒ ๋งŽ์Šต๋‹ˆ๋‹ค.

 

๋จผ์ €, ์Šคํฌ๋กค ์œ„์น˜๋ฅผ ์ €์žฅํ•˜๊ธฐ ์œ„ํ•ด ๊ฐ™์€ ํŽ˜์ด์ง€์ธ์ง€๋ฅผ ๊ตฌ๋ถ„ํ•˜๋Š” uniqueId๋ฅผ ์ƒ์„ฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด “๊ฐ™์€ ํŽ˜์ด์ง€”๋ผ๋Š” ๊ธฐ์ค€์ด ์กฐ๊ธˆ ๋ชจํ˜ธํ•œ๋ฐ, ๋ช‡ ๊ฐ€์ง€ ์˜ˆ๋ฅผ ๋“ค์–ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

  1. ๊ฐ€์ƒ ๋ฆฌ์ŠคํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” A ํŽ˜์ด์ง€์—์„œ ์Šคํฌ๋กค ์„ ๋‚ด๋ฆฐ ํ›„, B ํŽ˜์ด์ง€๋กœ ์ด๋™ํ–ˆ๋‹ค. ๊ทธ ํ›„, ๋’ค๋กœ๊ฐ€๊ธฐ ๋ฒ„ํŠผ์„ ๋ˆŒ๋Ÿฌ์„œ A ํŽ˜์ด์ง€๋กœ ๋Œ์•„์™”๋‹ค. (๊ฒฐ๊ณผ : A ํŽ˜์ด์ง€๊ฐ€ ๋ณด๋˜ ์Šคํฌ๋กค ์œ„์น˜๋กœ ๋ณต๊ท€๋˜์–ด ์žˆ์–ด์•ผ ํ•จ)
  2. ๊ฐ€์ƒ ๋ฆฌ์ŠคํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” A ํŽ˜์ด์ง€์—์„œ ์Šคํฌ๋กค ์„ ๋‚ด๋ฆฐ ํ›„, ์ƒˆ๋กœ๊ณ ์นจ ํ–ˆ๋‹ค. (๊ฒฐ๊ณผ : A ํŽ˜์ด์ง€๊ฐ€ ๋ณด๋˜ ์Šคํฌ๋กค ์œ„์น˜๋กœ ๋ณต๊ท€๋˜์–ด ์žˆ์–ด์•ผ ํ•จ)
  3. ๊ฐ€์ƒ ๋ฆฌ์ŠคํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” A ํŽ˜์ด์ง€์—์„œ ์Šคํฌ๋กค ์„ ๋‚ด๋ฆฐ ํ›„, B ํŽ˜์ด์ง€๋กœ ์ด๋™ํ–ˆ๋‹ค. ๊ทธ ํ›„, ์ฃผ์†Œ ์ฐฝ์— url์„ ์ง์ ‘ ์ž…๋ ฅํ•˜์—ฌ A ํŽ˜์ด์ง€๋กœ ๋Œ์•„์™”๋‹ค. (๊ฒฐ๊ณผ : A ํŽ˜์ด์ง€์˜ ์Šคํฌ๋กค์€ ์ดˆ๊ธฐํ™”๋˜์–ด ์žˆ์–ด์•ผ ํ•จ)

๊ฒฐ๊ตญ ์ƒˆ๋กœ๊ณ ์นจ์ด๋‚˜ ๋’ค๋กœ๊ฐ€๊ธฐ์™€ ๊ฐ™์ด ์ง€๊ธˆ A ํŽ˜์ด์ง€๋ฅผ ๋ณด๋˜ ์ค‘ ๋‹ค๋ฅธ ๊ณณ์— ๊ฐ”๋‹ค๊ฐ€ ๋˜๋Œ์•„์™”๋‹ค๋Š” ๊ฒƒ์ด ๋ณด์žฅ๋˜์–ด์•ผ ์ €์žฅ๋œ ์Šคํฌ๋กค ์œ„์น˜๋ฅผ ๋ฐ›์•„์™€ ์ด๋™ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด ๊ตฌํ˜„์€ history API์™€ ์—ฐ๊ฒฐ๋˜์–ด ์žˆ๋Š”๋ฐ, ์•„๋ž˜์™€ ๊ฐ™์ด history API์˜ pushState ํ•จ์ˆ˜๋ฅผ ์ด์šฉํ•ด state๋ฅผ ์ง‘์–ด๋„ฃ์œผ๋ฉด (1. ์ƒˆ๋กœ๊ณ ์นจ / 2. ๋’ค๋กœ๊ฐ€๊ธฐ) ์‹œ์—๋Š” state๊ฐ€ ์œ ์ง€๋˜๊ณ  (3. url ์ž…๋ ฅ ์ ‘์† ์‹œ)์—๋Š” state๊ฐ€ ๋‚ ์•„๊ฐ€ ๋ฒ„๋ฆผ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ์ด์šฉํ•˜์—ฌ ์ด์ „์— ์ƒ์„ฑํ•œ id์™€ ๊ฐ™์€ ํŽ˜์ด์ง€์ธ์ง€ ํŒ๋‹จํ•˜์—ฌ uniqueId๋ฅผ ์ƒ์„ฑํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ๊ฐœ๋ฐœํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

1) state ๋„ฃ๊ธฐ 2) ์ƒˆ๋กœ๊ณ ์นจ / ๋’ค๋กœ๊ฐ€๊ธฐ ์‹œ 3) ์ฃผ์†Œ์ฐฝ์— url ๋‹ค์‹œ ์ฐ๊ธฐ ์‹œ

 

๋‘๋ฒˆ์งธ๋กœ, ์ด๋™ํ•˜๊ณ ์žํ•˜๋Š” row ์ด์ „์˜ ์•„์ดํ…œ์ด ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ 50๋ฒˆ์งธ row๋กœ ๋ณต๊ท€ํ•œ๋‹ค๋ฉด ์œ„์— 49๊ฐœ์˜ row๊ฐ€ ์žˆ์„ ๋•Œ์˜ ๋†’์ด๊ฐ€ ๋ฏธ๋ฆฌ ์žกํ˜€์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋ณดํ†ต ์ด ์œ„์ชฝ๋†’์ด๋ฅผ ์ž„์‹œ๋กœ padding์œผ๋กœ ์žก์•„๋‘๊ณ , ์‹ค์ œ ์•„์ดํ…œ์„ ๋ Œ๋”๋งํ•  ๋•Œ๋งˆ๋‹ค ์ฐจ์ด๋ฅผ ๊ฐฑ์‹ ํ•ด๊ฐ€๋Š” ์‹์œผ๋กœ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.

์Šคํฌ๋กค ์œ„์น˜ ์ค‘๊ฐ„์œผ๋กœ ๋Œ์•„์™€์„œ ์—ญ๋ฐฉํ–ฅ์œผ๋กœ ์Šคํฌ๋กค์„ ์˜ฌ๋ผ๊ฐˆ ๋•Œ, ์œ„์™€ ๊ฐ™์ด ์ด๋ฏธ ๋†’์ด๊ฐ€ ์žกํ˜€์žˆ์–ด padding์„ ์ค„์ด๊ณ  ์•„์ดํ…œ์˜ ๋†’์ด๋ฅผ ๋†’์ด๋Š” ์‹์œผ๋กœ ๊ตฌํ˜„ํ•  ์ˆ˜๋„ ์žˆ์ง€๋งŒ, virtuoso์—์„œ์˜ prepending ์˜ˆ์ œ์™€ ๊ฐ™์ด ์•„๋ž˜๋กœ ์Šคํฌ๋กค์„ ๋‚ด๋ฆด ๋•Œ์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ์œ„๋กœ ์˜ฌ๋ฆด ๋•Œ๋„ ํ•œ row์”ฉ ๋ฐ›์•„์˜ค๋„๋ก ํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ์Šต๋‹ˆ๋‹ค. prepending์œผ๋กœ ๊ตฌํ˜„ํ•  ๊ฒฝ์šฐ ์˜์ƒ๊ณผ ๊ฐ™์ด ์ƒˆ๋กœ๊ณ ์นจ ์‹œ ์Šคํฌ๋กค ๋ฐ”์˜ ํฌ๊ธฐ๊ฐ€ ์ปค์กŒ๋‹ค๊ฐ€ ์œ„๋กœ ์Šคํฌ๋กค ํ•  ์ˆ˜๋ก ์ž‘์•„์ง€๋Š” ๊ฑธ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ƒˆ๋กœ๊ณ ์นจ ์‹œ ์Šคํฌ๋กค ๋ฐ”๊ฐ€ ์ปค์ง€๋Š” ํ˜„์ƒ

 

์Šคํฌ๋กค ๋ณต๊ท€ ๋กœ์ง์ด ํ•„์š”ํ•œ ์ด์œ ๊ฐ€ ๋‹ค์‹œ ๋Œ์•„์™”์„ ๋•Œ ์ด์ „๊ณผ ๊ฐ™์€ ๊ฒฝํ—˜์„ ์ฃผ๊ธฐ ์œ„ํ•ด์„œ๋ผ๋ฉด ์ „์ž์˜ padding์„ ์ทจํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์„ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

 

๋งˆ์ง€๋ง‰์œผ๋กœ, ์ด๋™ํ•˜๊ณ ์žํ•˜๋Š” row ์ด์ „์˜ ์•„์ดํ…œ ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋ถ€ ๋ฐ›์•„์˜ค์ง€๋Š” ๋ง์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๋ฐ”๋กœ ์ด์ „์˜ “์ด๋™ํ•˜๊ณ ์žํ•˜๋Š” row ์ด์ „์˜ ์•„์ดํ…œ์ด ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.” ๊ตฌํ˜„๊ณผ ํ—ท๊ฐˆ๋ฆด ์ˆ˜๋„ ์žˆ์„ ๊ฒƒ ๊ฐ™์€๋ฐ์š”. ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ๊ฒƒ๊ณผ ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜๋Š” ๊ฒƒ์€ ๋‹ค๋ฆ…๋‹ˆ๋‹ค. ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜๊ณ  padding์œผ๋กœ ์œ„์น˜๋Š” ์žก์•„๋‘์ง€๋งŒ ํ˜„์žฌ ์Šคํฌ๋กค์ด ๊ทธ๊ณณ์— ์œ„์น˜ํ•ด ์žˆ์ง€ ์•Š์œผ๋ฏ€๋กœ ์‹ค์ œ ์•„์ดํ…œ์€ ๋ Œ๋”๋ง๋˜์–ด ์žˆ์ง€์•Š๊ณ , ์‹ค์ œ ๋ฐ์ดํ„ฐ๋„ ๋ฐ›์•„์˜ค์ง€ ์•Š์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์ด ๋ถ€๋ถ„์„ ๊ฑฑ์ •ํ•˜๋Š” ๊ฒƒ์€ ๋‹จ์ˆœํ•˜๊ฒŒ ์œ„์—์„œ๋ถ€ํ„ฐ ์ˆœ์ฐจ์ ์œผ๋กœ ์Šคํฌ๋กค์„ ์ด๋™ํ•ด์„œ ํƒ€๊ฒŸ ์œ„์น˜์— ๋„์ฐฉํ•˜๋„๋ก ์Šคํฌ๋กค ๋ณต๊ท€ ๋กœ์ง์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ, ์ด๋ ‡๊ฒŒ ๋˜๋ฉด ์Šคํฌ๋กค์ด ๋‚ด๋ ค๊ฐ€๋Š” ๋™์•ˆ ๋ชจ๋“  ์•„์ดํ…œ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜ค๊ธฐ ๋•Œ๋ฌธ์— ๋ง‰์ƒ ํƒ€๊ฒŸ ์œ„์น˜์— ๋„์ฐฉํ–ˆ์„ ๋•Œ ๋ฐ์ดํ„ฐ๊ฐ€ ์•„์ง ๋ถˆ๋Ÿฌ์™€์ง€์ง€ ์•Š์€ ์ƒํƒœ์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๊ฒฝ์šฐ ์Šคํฌ๋กค์ด ์ผ์ • ์†๋„ ์ด์ƒ์œผ๋กœ ์›€์ง์ด๋ฉด ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜ค์ง€ ์•Š๋Š”๋‹ค๊ฑฐ๋‚˜, ํƒ€๊ฒŸ ์œ„์น˜์˜ ์•„์ดํ…œ์ด ๋ณด์ด๋„๋ก ํ™”๋ฉด์— ๋„์šฐ๊ณ  ์œ„์ชฝ์— padding์„ ์ฃผ๋Š” ๋ฐฉ์‹์œผ๋กœ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

์•„์ดํ…œ์˜ ๋†’์ด๊ฐ€ ์ž๋™์œผ๋กœ ์กฐ์ ˆ

์ด ๊ธฐ๋Šฅ์€ ์•„๋ž˜์˜ 3๊ฐ€์ง€ ๊ธฐ๋Šฅ์„ ํฌํ•จํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

  1. ํ•œ Row์—์„œ row์˜ ๋†’์ด๋Š” row์— ์†ํ•œ item ์ค‘ ๊ฐ€์žฅ ํฐ ๋†’์ด์— ๋งž์ถฐ ์กฐ์ •๋œ๋‹ค.
  2. ๊ฐ Row์˜ ๋†’์ด๋Š” ์„œ๋กœ ๋‹ค๋ฅผ ์ˆ˜ ์žˆ๋‹ค.
  3. item์˜ ๋†’์ด๊ฐ€ ๋„์ค‘ ๋ณ€ํ•  ๊ฒฝ์šฐ ์•„๋ž˜์ชฝ์˜ row๋“ค์€ ์•„๋ž˜๋กœ ๋ฐ€๋ฆฌ๊ฑฐ๋‚˜ ์œ„๋กœ ์˜ฌ๋ผ์˜ค๋Š” ๋“ฑ์˜ ์œ„์น˜ ๋ณ€ํ™”๋ฅผ ๊ฒช๊ฒŒ ๋˜์–ด, ๋‹ค์‹œ ๋ Œ๋”๋ง ํ•ด์ฃผ์–ด์•ผ ํ•œ๋‹ค.

ํ•ด๋‹น ๊ธฐ๋Šฅ์€ ์ƒ๊ฐ๋ณด๋‹ค ํ˜„์žฌ ๋‚˜์™€์žˆ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๊ตฌํ˜„ํ•˜๊ธฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ฝ”๋“œ ๊ตฌํ˜„์ด ๊ฝค๋‚˜ ๋ณต์žกํ•˜๊ณ  ๋‹ค๋ฅธ ๊ธฐ๋Šฅ๋“ค์—๋„ ์˜ํ–ฅ์„ ๋งŽ์ด ๋ผ์น˜์ง€ ๋•Œ๋ฌธ์— (์˜ํ–ฅ๋„๊ฐ€ ๋†’๊ธฐ ๋•Œ๋ฌธ์—) ๋˜๋„๋ก์ด๋ฉด ํ•ด๋‹น ๊ธฐ๋Šฅ์„ ์ง€์›ํ•˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

  • react-virtuoso (์ž๋™ ์ง€์›), react-virtualized (CellMeasure๋ฅผ ํ†ตํ•ด ์ง€์›)

 

ํ™”๋ฉด์— ๋ณด์ด๋Š” ์•„์ดํ…œ๋งŒ ๋ Œ๋”๋ง

ํ˜„์žฌ ํ™”๋ฉด์— ๋ณด์ด๋Š” ์•„์ดํ…œ๋งŒ ๋ Œ๋”๋งํ•˜๋Š” ๊ฒƒ์€ ๊ฐ€์ƒ ๋ฆฌ์ŠคํŠธ์˜ ๊ธฐ๋ณธ ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค. ๋‹ค๋งŒ, ์–ด๋Š ์ •๋„ ํ˜„์žฌ ํ™”๋ฉด์—์„œ ๋” ๋ณด์ด๊ฒŒ ํ•  ์ง€(overscan)๋ฅผ ์กฐ์ •ํ•  ์ˆ˜ ์žˆ์–ด์•ผ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์‹œ๊ฐ„, ๋ถˆ๋Ÿฌ์˜จ ๋ฐ์ดํ„ฐ๋กœ ์•„์ดํ…œ์„ ๋ Œ๋”๋งํ•˜๋Š” ์‹œ๊ฐ„๊นŒ์ง€ ๊ณ ๋ คํ•˜์—ฌ ์‚ฌ์šฉ์ž์—๊ฒŒ ๋น ๋ฅด๊ฒŒ ์ฝ˜ํ…์ธ ๋ฅผ ๋…ธ์ถœ ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

์—ด์˜ ์ˆ˜๋ฅผ ๋ฐ˜์‘ํ˜• ์œผ๋กœ ์กฐ์ •

๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์—์„œ List, Grid ๋ ˆ์ด์•„์›ƒ์„ ๋Œ€๋ถ€๋ถ„ ์ œ๊ณตํ•˜๋ฏ€๋กœ ํ•ด๋‹น ๊ธฐ๋Šฅ์€ Grid ๋ ˆ์ด์•„์›ƒ์„ ์ ์šฉํ•˜๋ฉด ๊ตฌํ˜„ํ•˜๊ธฐ ์‰ฝ์Šต๋‹ˆ๋‹ค.

 

 

์–‘๋ฐฉํ–ฅ ๋ฌดํ•œ ๋ฐ์ดํ„ฐ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ๊ณผ ์—ฐ๊ฒฐ ๐Ÿ”ฅ

(๋ฌดํ•œ ๋ฐ์ดํ„ฐ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ) ๊ธฐ๋Šฅ์€ ๊ฐ€์ƒ ๋ฆฌ์ŠคํŠธ์™€ ๋ณ„๊ฐœ์ธ hooks๋กœ ๊ตฌํ˜„ํ•ด์•ผ ํ•  ๋ถ€๋ถ„์ด์ง€๋งŒ, ์ž ์‹œ ๊ฐ„๋‹จํ•˜๊ฒŒ ๋‹ค๋ค„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ์ด ๊ธฐ๋Šฅ์€ hooks๋กœ ๊ตฌํ˜„๋˜๋ฉฐ ๊ฐ„๋‹จํ•˜๊ฒŒ ๊ฐ€์ƒ ๋ฆฌ์ŠคํŠธ์—์„œ ์‚ฌ์šฉํ•  ๋ฐ์ดํ„ฐ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค. ๋‚ด๋ถ€์—์„œ ์„œ๋น„์Šค ์ฝ”๋“œ๋กœ๋ถ€ํ„ฐ ์ฃผ์ž…๋ฐ›์€ api fetching ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๊ฒŒ ๋˜์–ด์žˆ์Šต๋‹ˆ๋‹ค.

๋ฌดํ•œ ๋ฐ์ดํ„ฐ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ๋Š” offset์„ ์ด์šฉํ•˜๋Š” ๋ฐฉ์‹๊ณผ cursor๋ฅผ ์ด์šฉํ•˜๋Š” ๋ฐฉ์‹์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ž์„ธํ•œ ์„ค๋ช…์€ ํ•ด๋‹น ๋ธ”๋กœ๊ทธ๋ฅผ ์ฐธ๊ณ ํ•ฉ์‹œ๋‹ค.

๊ฒฐ๊ณผ์ ์œผ๋กœ offset ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด hooks ๋‚ด๋ถ€์— ์ €์žฅํ•˜๊ณ  ์žˆ๋Š” ๋ฐ์ดํ„ฐ๋“ค์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ˜•์‹์„ ๊ฐ€์ง‘๋‹ˆ๋‹ค.

{
 data: T[], // ํŽ˜์ด์ง€ ๋ฐ์ดํ„ฐ
 state: 'non-load' | 'loaded', // ํŽ˜์ด์ง€ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ถˆ๋Ÿฌ์™€์ง„ ์ƒํƒœ
 nextPage: boolean, // ๋‹ค์Œ ํŽ˜์ด์ง€ ์œ ๋ฌด
}>;

state๋Š” ๋ฐ์ดํ„ฐ๊ฐ€ ์‹ค์ œ๋กœ ๋ถˆ๋Ÿฌ์™€ ์กŒ๋Š”์ง€๋ฅผ ์˜๋ฏธํ•˜๋Š”๋ฐ, non-load ์ƒํƒœ๋Š” ๊ฐ„๋‹จํžˆ ๋งํ•ด์„œ ๋ถˆ๋Ÿฌ์˜จ ์ฒ™ ํ•˜๋Š”(?) ์ƒํƒœ๋ฅผ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ๋‹ค๊ณ  ๊ฐœ์ˆ˜๋ฅผ ๋”ํ•˜์ง€๋งŒ ์‹ค์ œ๋กœ ๊บผ๋‚ด๋ณด๋ฉด ๋ฐ์ดํ„ฐ๊ฐ€ ์—†๋Š” ๊ทธ๋Ÿฐ ์ƒํƒœ์ž…๋‹ˆ๋‹ค. ๋น ๋ฅด๊ฒŒ ์Šคํฌ๋กค ํ–ˆ์„ ๋•Œ๋‚˜, ์Šคํฌ๋กค ๋ณต๊ท€ ์‹œ ์œ„์ชฝ row๋“ค์— ํ•ด๋‹นํ•˜๋Š” page์— ๋Œ€ํ•ด์„œ๋Š” ์ด๋Ÿฌํ•œ ์ƒํƒœ๋ฅผ ๊ฐ€์ง‘๋‹ˆ๋‹ค.

 

๊ฐ€์ƒ ๋ฆฌ์ŠคํŠธ์™€์˜ ์—ฐ๊ฒฐ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ด๋ฃจ์–ด์ง‘๋‹ˆ๋‹ค.

const { totalCount, loadItemList, getItem } = useInfiniteList<Data>({
  initialPage: INITIAL_PAGE,
  getItemList, // data fetching ํ•จ์ˆ˜
});

return (
  <VirtualizedList
    totalCount={totalCount} // ๋ฐ์ดํ„ฐ ์ด ๊ฐœ์ˆ˜
    loadItemList={loadItemList} // api๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ํ•จ์ˆ˜
    renderItem={(index) => { // ๋ Œ๋”๋ง ํ•จ์ˆ˜
      const item = getItem(index); // ์ €์žฅ๋œ ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ
      return (
        <div>
          {item?.index}
        </div>
      );
    }}
  />
);

 

Minor ๊ธฐ๋Šฅ

  • ํŽ˜์ด์ง€ ์žฌ ์ ‘์† ์‹œ ํŽ˜์ด์ง€ ๋„ˆ๋น„๊ฐ€ ๋ณ€๊ฒฝ ๋˜์—ˆ์Œ์„ ๊ณ ๋ คํ•˜์—ฌ ์Šคํฌ๋กค ์œ„์น˜ ์ฐพ๊ธฐ
  • ํŠน์ • ์•„์ดํ…œ์—๋งŒ ์Šคํƒ€์ผ ์ฃผ๊ธฐ
  • ์ค‘๊ฐ„์— ์ง€์ •๋œ ๋ฆฌ์ŠคํŠธ ์•„์ดํ…œ์ด ์•„๋‹Œ ๋‹ค๋ฅธ ์š”์†Œ ์ฃผ์ž…
  • ํ•€ํ„ฐ๋ ˆ์ŠคํŠธ ๊ฐ™์ด row์˜ ์‹œ์ž‘ ์œ„์น˜๊ฐ€ ์ƒ์ดํ•œ ๋ ˆ์ด์•„์›ƒ
  • ์Šคํฌ๋กค ์†๋„์— ๋”ฐ๋ผ placeholder UI ํ‘œ์ถœ

minor ๊ธฐ๋Šฅ๋“ค์€ ๊ธฐํš์— ๋”ฐ๋ผ ํ•„์š”ํ•  ์ˆ˜๋„ ์žˆ๋Š” ๊ธฐ๋Šฅ ๋“ค์ด๋ผ ๋”ฐ๋กœ ๊ตฌํ˜„ ๋ฐฉ๋ฒ•์„ ์„ค๋ช…ํ•˜์ง€๋Š” ์•Š๊ฒ ์Šต๋‹ˆ๋‹ค. ์ด์™ธ์—๋„ ๊ฐ€์ƒ ๋ฆฌ์ŠคํŠธ์™€ ๊ด€๋ จํ•ด์„œ๋Š” ๋‹ค์–‘ํ•œ ๋‹ˆ์ฆˆ๊ฐ€ ์ƒ๊ธธ ์ˆ˜ ์žˆ์–ด ๋ฐฉ๋Œ€ ํ•ด์งˆ ์ˆ˜ ์žˆ๋Š” ์ปดํฌ๋„ŒํŠธ์ž…๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ, ์ตœ๋Œ€ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ ๊ตฌํ˜„ ๋ฐ ์œ ์ง€๋ณด์ˆ˜๊ฐ€ ํž˜๋“  ๊ธฐ๋Šฅ๋“ค์„ ์œ„์ž„ํ•˜๋Š” ์‹์œผ๋กœ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

๋‹ค์Œ ์ฑ•ํ„ฐ ์—์„œ๋Š” ๊ฐ€์ƒ๋ฆฌ์ŠคํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋“ค์„ ์†Œ๊ฐœํ•˜๊ณ  ๊ฐ์ž ์–ด๋–ค ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๊ธฐ ํŽธํ•œ ์ง€, ์–ด๋–ค ์žฅ๋‹จ์ ์ด ์žˆ๋Š” ์ง€ ๋น„๊ตํ•ด๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

 

 ๋ถ€์ •ํ™•ํ•œ ์ •๋ณด๊ฐ€ ๋‹ด๊ฒจ์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค! 

 ๋Œ“๊ธ€๋กœ ์•Œ๋ ค์ฃผ์„ธ์š” :) 

 

๋ฐ˜์‘ํ˜•