๐ŸŽจ 09. ๋ Œ๋” ์ตœ์ ํ™”์˜ ๋‹ฌ์ฝคํ•œ ๊ฑฐ์ง“๋ง (useMemo, useCallback)

๐Ÿ“‹ ๊ฐœ์š”

useMemo์™€ useCallback์„ ๋ฌด๋ถ„๋ณ„ํ•˜๊ฒŒ ๋‚จ๋ฐœํ•  ๋•Œ ๋ฐœ์ƒํ•˜๋Š” ๋ฉ”๋ชจ๋ฆฌ ์˜ค๋ฒ„ํ—ค๋“œ์™€, ์ง„์ •ํ•œ ์ตœ์ ํ™”์˜ ํƒ€์ด๋ฐ์„ ๋ฐฐ์šฐ๋Š” ๊ฐ€์ด๋“œ์ž…๋‹ˆ๋‹ค.

๐ŸŽฏ ์ด ์„น์…˜์„ ์ฝ๊ณ  ๋‚˜๋ฉด:

  • ์ตœ์ ํ™” ํ›…(useMemo, useCallback)์ด ๊ณต์งœ๊ฐ€ ์•„๋‹ˆ๋ฉฐ ์˜คํžˆ๋ ค ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ณ‘๋“ค๊ฒŒ ํ•˜๋Š” ์›๋ฆฌ๋ฅผ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • React.memo์™€ ๊ฒฐํ•ฉ๋˜์ง€ ์•Š์€ useCallback์ด ์–ผ๋งˆ๋‚˜ ๋ฌด์˜๋ฏธํ•œ ์ง“์ธ์ง€ ๊นจ๋‹ซ๊ฒŒ ๋œ๋‹ค.
  • "์„ฃ๋ถ€๋ฅธ ์ตœ์ ํ™”๋Š” ๋งŒ์•…์˜ ๊ทผ์›"์ด๋ผ๋Š” ์›์น™ํ•˜์—, ์–ธ์ œ ์ง„์งœ ์ด ํ›…๋“ค์„ ๊บผ๋‚ด๋“ค์–ด์•ผ ํ• ์ง€ ๋ช…ํ™•ํ•œ ๊ธฐ์ค€์„ ํ™•๋ฆฝํ•œ๋‹ค.

๐Ÿ“‹ ๋ชฉ์ฐจ


๐Ÿ“Œ ์ด ๋ฌธ์„œ๋ฅผ ์ฝ๊ธฐ ์ „์—

โฑ๏ธ ์˜ˆ์ƒ ์ฝ๊ธฐ ์‹œ๊ฐ„: 15๋ถ„ / ํ•ต์‹ฌ ํŒŒํŠธ: 10๋ถ„

๐Ÿ—บ๏ธ ์ด ๋ฌธ์„œ์˜ ๋ฐฐ๊ฒฝ ์„ธ๊ณ„๊ด€: '์˜์ˆ˜๋„ค ์ปค๋ฎค๋‹ˆํ‹ฐ'

  • ์˜์ฒ (์‹ ์ž…):"์„ ๋ฐฐ๋‹˜! ์ œ๊ฐ€ ์šฐ๋ฆฌ ์ปค๋ฎค๋‹ˆํ‹ฐ ์•ฑ์„ ์—„์ฒญ๋‚˜๊ฒŒ ์ตœ์ ํ™”ํ–ˆ์–ด์š”. ๋ชจ๋“  ํ•จ์ˆ˜๋Š” useCallback์œผ๋กœ ๊ฐ์‹ธ๊ณ , ๋ชจ๋“  ๊ฐ์ฒด์™€ ๋ฐฐ์—ด์€ useMemo๋กœ ๊ฐ์ŒŒ์Šต๋‹ˆ๋‹ค! ์ด์ œ ์—„์ฒญ ๋น ๋ฅด๊ฒ ์ฃ ?"
  • ์˜ํ˜ธ(๋ฆฌ๋“œ): "์˜์ฒ  ๋‹˜... ์ง€๊ธˆ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ๋‘ ๋ฐฐ, ์„ธ ๋ฐฐ๋กœ ๊นŽ์•„๋จน์œผ๋ฉด์„œ '์ตœ์ ํ™”'๋ผ๊ณ  ๋ถ€๋ฅด์‹œ๋Š” ๊ฑด๊ฐ€์š”? ๋ธŒ๋ผ์šฐ์ € ๋ฉ”๋ชจ๋ฆฌ ํƒญ์ด ํ„ฐ์ง€๊ธฐ ์ง์ „์ž…๋‹ˆ๋‹ค."

๐Ÿค” ์™œ ์•Œ์•„์•ผ ํ•˜๋Š”๊ฐ€: '์ตœ์ ํ™”'๋ผ๋Š” ๋ฌดํ˜•์˜ ๊ณตํฌ

ํ”„๋ก ํŠธ์—”๋“œ ์‹ค๋ฌด ๊ธฐ์ˆ  ๋ฉด์ ‘์—์„œ ์ฃผ๋‹ˆ์–ด๋“ค์ด ๊ฐ€์žฅ ๋งŽ์ด ํƒˆ๋ฝํ•˜๋Š” ์งˆ๋ฌธ์ด ์žˆ์Šต๋‹ˆ๋‹ค. "์–ด๋А ์ƒํ™ฉ์—์„œ useMemo์™€ useCallback์„ ์“ฐ๋‚˜์š”?"
๋Œ€๋ถ€๋ถ„์€ "๋ Œ๋”๋ง ์„ฑ๋Šฅ์„ ๋†’์ด๊ธฐ ์œ„ํ•ด์„œ์š”", "๋งค๋ฒˆ ํ•จ์ˆ˜ ์ƒˆ๋กœ ๋งŒ๋“ค์–ด์ง€๋Š” ๊ฑธ ๋ง‰์œผ๋ ค๊ณ ์š”" ๋ผ๊ณ  ๋‹ต๋ณ€ํ•ฉ๋‹ˆ๋‹ค. ์ด ๋Œ€๋‹ต์€ ๋ฐ˜์€ ๋งž๊ณ  ๋ฐ˜์€ ํ‹€๋ ธ์Šต๋‹ˆ๋‹ค (์‚ฌ์‹ค ๊ฑฐ์˜ ํ‹€๋ ธ์Šต๋‹ˆ๋‹ค).

๐Ÿค” ์ž ๊น, ๋จผ์ € ์ƒ๊ฐํ•ด๋ด
๋ง์…ˆ์„ ํ•œ ๋ฒˆ ํ•˜๋Š” ํ•จ์ˆ˜๊ฐ€ ์žˆ์–ด. ์ด๊ฑธ useCallback์œผ๋กœ ๊ฐ์‹ธ๋Š” ๊ฒŒ ๊ทธ๋ƒฅ ์ƒ์งœ๋กœ ๋ƒ…๋‘๋Š” ๊ฒƒ๋ณด๋‹ค ๋” ๋น ๋ฅผ๊นŒ?

// โŒ ์˜์ฒ ์ด์˜ ๋ฌด์ง€์„ฑ ์ตœ์ ํ™” (Premature Optimization)
function Counter() {
  const [count, setCount] = useState(0);
 
  // ์˜์ฒ : "์™€! ํ•จ์ˆ˜๊ฐ€ ์ž๊พธ ๋ถˆํ•„์š”ํ•˜๊ฒŒ ์ƒ์„ฑ๋˜๋Š” ๊ฑธ ๋ง‰์•„์•ผ ํ•ด!"
  const handleClick = useCallback(() => {
    setCount(c => c + 1);
  }, []);
 
  return <button onClick={handleClick}>ํด๋ฆญ {count}</button>;
}

์˜์ฒ ์ด๋Š” ์ด ์ฝ”๋“œ๋ฅผ ์งœ๊ณ  ์Šค์Šค๋กœ๋ฅผ ์ตœ์ ํ™” ๋งˆ์Šคํ„ฐ๋ผ ๋ฟŒ๋“ฏํ•ดํ•˜๊ฒ ์ง€๋งŒ, ๋ฆฌ์•กํŠธ ์ฐฝ์‹œ์ž๋“ค์€ ์ด ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด ํ˜€๋ฅผ ๋Œ์ฏง ์ฐน๋‹ˆ๋‹ค. ์ด ์ฝ”๋“œ๋Š” ํ›…์„ ์•ˆ ์“ด ์ฝ”๋“œ๋ณด๋‹ค ํ›จ์”ฌ, ๋ฌด์กฐ๊ฑด ๋” ๋А๋ฆฌ๊ณ  ๋”์ฐํ•œ ์ฝ”๋“œ์ด๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.


๐Ÿ—๏ธ ๋น„์œ ๋กœ ๋จผ์ € ์ดํ•ดํ•˜๊ธฐ

๐Ÿง’ 5์‚ด์—๊ฒŒ ์„ค๋ช…ํ•œ๋‹ค๋ฉด?
์ผ๋ฐ˜ ํ•จ์ˆ˜ ์ •์˜ํ•˜๊ธฐ: ์—„๋งˆ๊ฐ€ ๋ฉ”๋ชจ์ง€์— "์‚ฌ๊ณผ ์‚ฌ ์˜ค๊ธฐ"๋ผ๊ณ  ๊ฐ€๋ณ๊ฒŒ ํœ˜๊ฐˆ๊ฒจ ์จ์„œ ์ง€๊ฐ‘์— ๋„ฃ์–ด๋‘๋Š” ๊ฒƒ. (๋ฉ”๋ชจ์ง€ ํ•œ ์žฅ์งœ๋ฆฌ, 0.0001์ดˆ๋ฉด ์”€)
useCallback ์“ฐ๊ธฐ: "์‚ฌ๊ณผ ์‚ฌ ์˜ค๊ธฐ"๋ผ๋Š” ๋ฉ”๋ชจ์ง€๋ฅผ ๊ณต์ฆ ์‚ฌ๋ฌด์†Œ(React ์—”์ง„ ๋‚ด์žฅ ๋ฉ”๋ชจ๋ฆฌ ์บ์‹œ ํƒ์ƒ‰ ์ฒด๊ณ„)์— ๊ฐ€์ ธ๊ฐ€์„œ, ๋„์žฅ์„ ์พ… ์ฐ๊ณ , ๋‘๊บผ์šด ์บ๋น„๋‹›(๋ฐฐ์—ด)์„ ์—ด์–ด ์ €๋ฒˆ์— ๋„ฃ์–ด๋‘” ๋ฉ”๋ชจ์ง€๋ž‘ ๋‚ด์šฉ์ด ๋˜‘๊ฐ™์€์ง€ ํ•˜๋‚˜ํ•˜๋‚˜ ๋Œ€์กฐํ•ด ๋ณธ ๋’ค, ๋˜‘๊ฐ™์œผ๋ฉด ์˜›๋‚  ๋ฉ”๋ชจ์ง€๋ฅผ ๊บผ๋‚ด์ฃผ๋Š” ํ–‰์œ„.

๊ณ ์ž‘ "์‚ฌ๊ณผ ํ•˜๋‚˜ ์‚ฌ ์™€"๋ผ๋Š” 1์ดˆ์งœ๋ฆฌ ๊ณ„์‚ฐ์‹์„ ์œ„ํ•ด ์ € ๊ฑฐ์ฐฝํ•œ ๊ณต์ฆ ์‚ฌ๋ฌด์†Œ๋ฅผ ๋ฐฉ๋ฌธํ•˜๋Š” ๊ฒŒ ๋งž์„๊นŒ? ์•„๋‹ˆ์ง€. ๋ฐฐ๋ณด๋‹ค ๋ฐฐ๊ผฝ์ด ํ›จ์”ฌ ์ปค์ง€์ž–์•„. ๊ทธ๋ƒฅ 1์ดˆ ๊ฑธ๋ ค์„œ ๋ฉ”๋ชจ์ง€ ์ฟจํ•˜๊ฒŒ ์ฐข๊ณ  ์ƒˆ๋กœ ํœ˜๊ฐˆ๊ฒจ ์“ฐ๋Š” ๊ฒŒ ๋ฐฑ๋งŒ ๋ฐฐ๋Š” ๋” ์ด๋“์ด์•ผ!

โœ… ํ•ต์‹ฌ ์›๋ฆฌ:
useMemo์™€ useCallback์€ ๊ณต์งœ ๋งˆ๋ฒ•์ด ์•„๋‹™๋‹ˆ๋‹ค. ํ•ด๋‹น ํ›…์„ ์‹คํ–‰ํ•˜๊ธฐ ์œ„ํ•ด ๋ฆฌ์•กํŠธ๋Š” ์˜์กด์„ฑ ๋ฐฐ์—ด([])์— ๋“ค์–ด๊ฐ„ ๊ฐ’๋“ค์„ ๋ฃจํ”„๋ฅผ ๋Œ๋ฉฐ ์ด์ „ ๊ฐ’๊ณผ ์ผ์ผ์ด ๋น„๊ต(Object.is)ํ•ด์•ผ ํ•˜๊ณ , ํž™ ๋ฉ”๋ชจ๋ฆฌ์˜ ํŠน์ˆ˜ํ•œ ์บ์‹œ ๊ณต๊ฐ„์„ ๋ณ„๋„๋กœ ์ ์œ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
์ฆ‰, "ํ›…์„ ์‹คํ–‰ํ•˜๊ณ  ๊ณ„์‚ฐ์„ ์บ์‹ฑํ•˜๋Š” ๋น„์šฉ(Overhead)"์ด "๋งค ๋ Œ๋”๋ง๋งˆ๋‹ค ๋‹จ์ˆœํžˆ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ํ•จ์ˆ˜/๊ฐ์ฒด๋ฅผ ํ•˜๋‚˜ ์ƒˆ๋กœ ๋ถ•์–ด๋นต ์ฐ์–ด๋‚ด๊ณ  ๊ฐ€๋น„์ง€ ์ปฌ๋ ‰ํ„ฐ(GC)์— ๋ฒ„๋ฆฌ๋Š” ๋น„์šฉ"๋ณด๋‹ค ์••๋„์ ์œผ๋กœ ๋น„์Œ‰๋‹ˆ๋‹ค. ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ์˜ V8 ์—”์ง„์€ ํ•จ์ˆ˜ ํ• ๋‹น๊ณผ ์“ฐ๋ ˆ๊ธฐ ์ฒ˜๋ฆฌ๋ฅผ ์ƒ์ƒ์„ ์ดˆ์›”ํ•  ์ •๋„๋กœ ๋ฏธ์นœ ๋“ฏ์ด ์ž˜ํ•ฉ๋‹ˆ๋‹ค.


๐Ÿงฉ ๊ทธ๋Ÿผ ์ง„์งœ๋Š” ์–ด๋””์— ์“ฐ๋Š”๊ฐ€? (์ •ํ™•ํ•œ ์‚ฌ์šฉ ํƒ€์ด๋ฐ)

ํƒ€์ด๋ฐ 1. [์ง„์งœ ๋ฌด๊ฑฐ์šด] ์—ฐ์‚ฐ์˜ ์บ์‹ฑ (useMemo)

100์›์งœ๋ฆฌ ๋ง์…ˆ(๋ฒ„ํŠผ ํด๋ฆญ ํ•จ์ˆ˜ ๋“ฑ)์€ ๊ทธ๋ƒฅ ๋ Œ๋”๋ง๋งˆ๋‹ค ์ƒˆ๋กœ ๋ฒ„๋ฆฌ๊ณ  ๋งŒ๋“œ์„ธ์š”.
ํ•˜์ง€๋งŒ "์ƒํ’ˆ ๋ฐ์ดํ„ฐ 1๋งŒ ๊ฐœ ์ •๋ ฌ", "3D WebGL ํŒŒ์‹ฑ ๋ฐ์ดํ„ฐ ๊ณ„์‚ฐ" ๊ฐ™์€ ๊ฒƒ๋“ค์€ ๋ฌด์กฐ๊ฑด ์บ์‹ฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

// โœ… ์˜ฌ๋ฐ”๋ฅธ ํƒ€์ด๋ฐ: CPU๊ฐ€ ๋น„๋ช…์„ ์ง€๋ฅด๋Š” ์—ฐ์‚ฐ (Pro Approach)
function AnalyticsDashboard({ userData }) {
  const [tab, setTab] = useState('chart');
 
  // userData๊ฐ€ ๋ฐ”๋€” ๋•Œ๋งŒ ์ด ์ง€์˜ฅ์˜ 1์ดˆ์งœ๋ฆฌ ๋ฃจํ”„ ์—ฐ์‚ฐ์„ ์ˆ˜ํ–‰ํ•˜๊ณ ,
  // ํƒญ(tab)์ด ๋ฐ”๋€” ๋•Œ๋Š” ์ด ์—ฐ์‚ฐ์„ ์™ ํ”ผํ•ด์„œ ์บ์‹œ๋œ ์ด์ „ ๊ฒฐ๊ณผ๋ฅผ ์žฌํ™œ์šฉํ•œ๋‹ค!
  const heavyProcessedData = useMemo(() => {
    return runHeavyMathAlgorithm(userData); // ์‹คํ–‰์— 500ms ๊ฑธ๋ฆฌ๋Š” ๋ฏธ์นœ ํ•จ์ˆ˜
  }, [userData]);
 
  return <div>...</div>;
}

ํƒ€์ด๋ฐ 2. ์ž์‹ ์ปดํฌ๋„ŒํŠธ์˜ ๋ Œ๋”๋ง ํญํฌ์ˆ˜ ๋ฐฉ์–ด (useCallback + React.memo)

๊ฐ€์žฅ ํ”ํ•˜๊ฒŒ useCallback์„ ์˜ค๋‚จ์šฉํ•˜๋Š” ์ผ€์ด์Šค๊ฐ€, "๊ทธ๋ƒฅ ์ž์‹ ์š”์†Œํ•œํ…Œ ์ธ๋ผ์ธ ํ•จ์ˆ˜ ์•ˆ ๋„˜๊ธฐ๋ ค๊ณ ์š”"์ž…๋‹ˆ๋‹ค.
onClick={handlePlay}๋กœ ํ•จ์ˆ˜๋ฅผ ๊ทธ๋ƒฅ ๋„˜๊ธฐ๋“  useCallback์œผ๋กœ ๊ฐ์‹ธ์„œ ๋„˜๊ธฐ๋“ , ์ž์‹ ์ปดํฌ๋„ŒํŠธ์ธ <Button />์€ ๋ถ€๋ชจ ๋ Œ๋”๋ง ์‹œ ๋ฌด.์กฐ.๊ฑด ๋˜‘๊ฐ™์ด ๋ฆฌ๋ Œ๋”๋ง๋ฉ๋‹ˆ๋‹ค!

useCallback์ด ์ง„์งœ ํž˜์„ ๋ฐœํœ˜ํ•˜๋ ค๋ฉด, ๋ฐ›๋Š” ์ž์‹ ์ปดํฌ๋„ŒํŠธ๊ฐ€ React.memo๋ผ๋Š” ์ฒ ๊ฐ‘์„ ๋‘๋ฅด๊ณ  ์žˆ์–ด์•ผ ๋น„๋กœ์†Œ ์œ ํšจํƒ€๊ฐ€ ๋“ค์–ด๊ฐ‘๋‹ˆ๋‹ค.

// ๐ŸŽฏ ์ž์‹ ์ปดํฌ๋„ŒํŠธ์— ์ฒ ๊ฐ‘(React.memo)์„ ๋‘˜๋ €๋‹ค!
// props๊ฐ€ ๋˜‘๊ฐ™๋‹ค๋ฉด ๋‚ด ๋ Œ๋”๋ง์€ ์ ˆ๋Œ€ ์•ˆ ํ•  ๊ฑฐ์•ผ!
const HeavyChart = React.memo(({ onZoom }) => {
  return <div>์ดˆ๊ณ ํ•ด์ƒ๋„ ๋ฌด๊ฑฐ์šด ์ฐจํŠธ</div>;
});
 
function Dashboard() {
  const [text, setText] = useState('');
 
  // โœ… ์™„๋ฒฝํ•œ ์ตœ์ ํ™”:
  // 1. useCallback์œผ๋กœ onZoom ํ•จ์ˆ˜์˜ ๊ป๋ฐ๊ธฐ(์ฃผ์†Œ๊ฐ’)๋ฅผ ์–ผ๋ฆฐ๋‹ค.
  // 2. HeavyChart๋Š” React.memo ๋•๋ถ„์— onZoom ์ฃผ์†Œ๊ฐ’์ด ๊ทธ๋Œ€๋กœ์ด๋ฏ€๋กœ ๋ Œ๋”๋ง์„ ๋ฉˆ์ถ˜๋‹ค!
  // ๊ฒฐ๊ณผ: ํ…์ŠคํŠธ ๋ฐ•์Šค์— ๊ธ€์„ ์น  ๋•Œ๋งˆ๋‹ค ์ด ๋ฌด๊ฑฐ์šด ์ฐจํŠธ๊ฐ€ ๋ Œ๋”๋ง๋˜๋Š” ๋Œ€์žฌ์•™์„ ์™„๋ฒฝํžˆ ๋ฐฉ์–ด!
  const handleZoom = useCallback((level) => {
    console.log("์คŒ ์‹œ๋„!", level);
  }, []);
 
  return (
    <>
      <input value={text} onChange={e => setText(e.target.value)} />
      <HeavyChart onZoom={handleZoom} />
    </>
  );
}

ํƒ€์ด๋ฐ 3. ๋‚จ์˜ Effect์˜ ์˜์กด์„ฑ ๋ณ€์ˆ˜๋กœ ๋˜์ ธ์งˆ ๋•Œ

์ด๊ฑด ์ด์ „ ๊ฐ€์ด๋“œ 06์—์„œ ๋ฐฐ์› ์ง€? ๊ฐ์ฒด๋‚˜ ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค์–ด์„œ ์ž์‹ ์ปดํฌ๋„ŒํŠธ์˜ useEffect ์˜์กด์„ฑ ๋ฐฐ์—ด([ ])๋กœ ๋˜์ ธ๋ฒ„๋ฆฌ๋ฉด ๋ฌดํ•œ ๋ฃจํ”„ ํญํƒ„์ด ํ„ฐ์งˆ ์ˆ˜ ์žˆ์–ด. ์ด๋•Œ ๊ทธ๊ฑธ ๋ฐฉ์–ดํ•˜๊ธฐ ์œ„ํ•œ ๋ฐฉํŒจ๋ง‰์ด์šฉ์œผ๋กœ useMemo/useCallback์„ ์‚ฌ์šฉํ•ด ์ฐธ์กฐ๋ฅผ ์–ผ๋ ค์ฃผ๋Š” ๊ฑฐ์ง€.


๐Ÿ’ฅ ์—๋Ÿฌ ํ•ด๊ฒฐ ์นดํƒˆ๋กœ๊ทธ

โŒ ๊ฐ์ฒด์™€ ๋ฐฐ์—ด์„ React.memo ์ž์‹์—๊ฒŒ ์ƒ์งœ๋กœ ๋„˜๊ธธ ๋•Œ

์ƒํ™ฉ: ์˜์ฒ ์ด๊ฐ€ ์ฐจํŠธ ์ปดํฌ๋„ŒํŠธ๋ฅผ React.memo๋กœ ๊ธฐ๊ป ๊ฐ์‹ธ๋†จ์–ด. ๊ทธ๋Ÿฐ๋ฐ๋„ ์ฐจํŠธ๊ฐ€ ๊ณ„์† ๋ฆฌ๋ Œ๋”๋ง์ด ๋œ๋Œ€!

const Chart = React.memo(({ data, options }) => { ... });
 
function Dashboard() {
  const [count, setCount] = useState(0);
 
  return (
    <div>
      <button onClick={() => setCount(c => c+1)}>์—…๋Žƒ</button>
      {/* ๐Ÿšจ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด Chart๊ฐ€ ์—ฌ์ „ํžˆ ๋ฏธ์นœ๋“ฏ์ด ๋ฆฌ๋ Œ๋”๋ง ๋จ! */}
      <Chart
        data={[1, 2, 3]}
        options={{ color: 'red' }}
      />
    </div>
  );
}

์›์ธ: React.memo๋Š” props๋ฅผ ์–•์€ ๋น„๊ต(Object.is)ํ•ด.
๋ฒ„ํŠผ์„ ๋ˆŒ๋Ÿฌ์„œ ๋ Œ๋”๋ง ์Šค๋ƒ…์ƒท์ด ๊ฐฑ์‹ ๋  ๋•Œ๋งˆ๋‹ค, ๋ถ€๋ชจ ํŠธ๋ฆฌ์˜ JSX ์ฝ”๋“œ ์•ˆ์— ์ ํ˜€ ์žˆ๋˜ [1, 2, 3]์ด๋ผ๋Š” ๋†ˆ์€ ๋ฉ”๋ชจ๋ฆฌ(Heap)์— ์™„์ „ํžˆ ์ƒˆ๋กœ์šด ์ฃผ์†Œ๋ฅผ ๊ฐ€์ง„ ๋ฐฐ์—ด๋กœ ์žฌํƒ„์ƒํ•ด! options ๊ฐ์ฒด๋„ ๋งˆ์ฐฌ๊ฐ€์ง€์•ผ.
React.memo ๋ด‡: "์•—! data(๋ฐฐ์—ด ๊ป๋ฐ๊ธฐ) ์ฃผ์†Œ๊ฐ€ ๋ฐ”๋€Œ์—ˆ๋„ค! ๋‹ค์‹œ ๊ทธ๋ ค!" ๋ฐฉ์–ด๋ง‰์ด ์ข…์ž‡์žฅ์ฒ˜๋Ÿผ ๋ฌด๋„ˆ์ง„ ๊ฑฐ์•ผ.

ํ•ด๊ฒฐ์ฑ…:
๊ฐ์ฒด ๋ฐฐ์—ด์„ ์˜๊ตฌ ๋™๊ฒฐ์‹œ์ผœ ๋„˜๊ฒจ๋ผ!

  // โœ… ๋ฐ–์œผ๋กœ ๋นผ๋‚ด๊ฑฐ๋‚˜(์ •์  ๋ฐ์ดํ„ฐ)
  const CHART_DATA = [1, 2, 3];
 
  // โœ… ๋‚ด๋ถ€์—์„œ ์จ์•ผ ํ•œ๋‹ค๋ฉด useMemo๋กœ ์–ผ๋ฆฌ๊ธฐ
  const chartOptions = useMemo(() => ({ color: 'red' }), []);
 
  return <Chart data={CHART_DATA} options={chartOptions} />;

๐Ÿ ์ด๋ฒˆ์— ๋ฐฐ์šด ๋‚ด์šฉ ์ด์ •๋ฆฌ

์ตœ์ ํ™”์˜ ๋Œ€์ƒโŒ ํ‹€๋ฆฐ ์ด์œ โœ… ์ง„์งœ ํƒ€๊ฒฉํŒ์ • ๊ฐ€์ด๋“œ
๋ก๊น ๋ฒ„ํŠผ๋“ค์˜ onClick ํ•จ์ˆ˜๋“คuseCallback ๋–ก์น  (์บ์‹ฑ ์˜ค๋ฒ„ํ—ค๋“œ๊ฐ€ ๋” ํผ, ์“ฐ๋ ˆ๊ธฐ GC๊ฐ€ ๋” ๋น ๋ฆ„)๊ทธ๋ƒฅ ํ‰๋ฒ”ํ•˜๊ฒŒ ํ™”์‚ดํ‘œ ํ•จ์ˆ˜ ๋˜์ง€๊ธฐ (() => setCount(c+1))
API ์‘๋‹ต 10๊ฐœ ์ฒ˜๋ฆฌ ๋“ฑ ์ผ๋ฐ˜ ๋กœ์งuseMemo ๋–ก์น  (๋ฉ”๋ชจ๋ฆฌ๋งŒ ์—„์ฒญ ๋จน์Œ)๊ทธ๋ƒฅ ๋ Œ๋”๋ง ๋„์ค‘ const result = ...๋กœ ๋ฐ”๋กœ ์ฒ˜๋ฆฌ
์ž์‹ ์š”์†Œ๋กœ ๋‚ด๋ ค๋ณด๋‚ด๋Š” ๊ฐ์ฒด/ํ•จ์ˆ˜์ž์‹ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ผ๋ฐ˜ ํ•จ์ˆ˜ํ˜•์ž„ (React.memo ์—†์œผ๋ฉด useCallback ์จ๋ดค์ž ๋ฆฌ๋ Œ๋”๋ง ๋จ)๋ฐ˜๋“œ์‹œ ์ž์‹์„ React.memo๋กœ ๊ฐ์‹ผ ํ›„, ๊ทธ ํ”„๋กญ์Šค๋ฅผ useCallback/useMemo๋กœ ์–ผ๋ ค์„œ ํ•˜์‚ฌํ•˜๊ธฐ!

๐Ÿ’ก ํ•œ ์ค„๋กœ ๊ธฐ์–ตํ•˜๊ธฐ
"์„ฃ๋ถ€๋ฅธ ์ตœ์ ํ™”๋Š” ๋งŒ์•…์˜ ๊ทผ์›์ด๋‹ค." (๋„๋„๋“œ ํฌ๋ˆ„์Šค)
useMemo์™€ useCallback์€ ๋‚ด ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ฒ„๋ฒ…๊ฑฐ๋ ค์„œ ๋„์ €ํžˆ ๋ชป ๋ด์ฃผ๊ฒ ์„ ๋•Œ, ํ”„๋กœํŒŒ์ผ๋Ÿฌ(Profiler)๋ฅผ ์ผœ์„œ ๋ Œ๋”๋ง ๋ฒ”์ธ์„ ์ฐพ์•„๋‚ธ ๋’ค์—๋งŒ ํ—ˆ๊ฐ€๋˜๋Š” ๋ณ‘๊ธฐ(Weapon)๋‹ค. ๋ฐฉ์–ด๊ตฌ(๊ธฐ๋ณธ ์žฅ๋น„)๊ฐ€ ์ ˆ๋Œ€ ์•„๋‹ˆ๋‹ค.


๐Ÿฃ ์˜์ฒ ์ด์˜ ํ‡ด๊ทผ ์ผ๊ธฐ

๋‚ด๊ฐ€ ์˜จ ์‚ฌ๋ฐฉ์— ๋ฐœ๋ผ๋‘” useCallback์ด ์‚ฌ์‹ค์€ ์•ฑ์„ ๋” ๋А๋ฆฌ๊ฒŒ ๋งŒ๋“ค๊ณ  ์žˆ์—ˆ๋‹ค๋‹ˆ... ํ•จ์ˆ˜ ์ƒˆ๋กœ ๋งŒ๋“œ๋Š” ๊ฒŒ ๊ณต์ฆ ์‚ฌ๋ฌด์†Œ ๊ฐ€๋Š” ๊ฒƒ๋ณด๋‹ค ๋ฐฑ๋งŒ ๋ฐฐ ์ด๋“์ด๋ผ๋Š” ๋น„์œ ๊ฐ€ ๋ผˆ๋ฅผ ๋•Œ๋ ธ๋‹ค.

๐Ÿ’ก "ํ›…(Hook)์€ ๊ณต์งœ๊ฐ€ ์•„๋‹ˆ๋‹ค! ์ •๋ง ๋ฌด๊ฑฐ์šด ์—ฐ์‚ฐ์„ ์บ์‹ฑํ•˜๊ฑฐ๋‚˜, React.memo๋ฅผ ๋‘๋ฅธ ์ž์‹์—๊ฒŒ ๊ป๋ฐ๊ธฐ๋ฅผ ๋ณด์กดํ•ด์„œ ๋„˜๊ธธ ๋•Œ๋งŒ ๊บผ๋‚ด๋“œ๋Š” ๋น„์žฅ์˜ ๋ฌด๊ธฐ๋‹ค."

์ตœ์ ํ™”๋ž์‹œ๊ณ  ๋–ก์น ํ–ˆ๋˜ ์ฝ”๋“œ๋“ค์ด ์‚ฌ์‹ค์€ ๋ฉ”๋ชจ๋ฆฌ์˜ ๋ฌด๋ค์ด์—ˆ๋‹ค๋‹ˆ ์–ผ๊ตด์ด ํ™”๋ˆ๊ฑฐ๋ฆฐ๋‹ค. ๊ทธ๋ž˜๋„ ์˜ํ˜ธ๋‹˜ ๋•์— "์ง„์งœ ํƒ€๊ฒฉ ํŒ์ •"์„ ๋ฐฐ์› ์œผ๋‹ˆ๊นŒ ์•„๊น์ง€ ์•Š๋‹ค. ๋‚ด์ผ ๋‹น์žฅ ์“ฐ๋ ˆ๊ธฐ ํ›…๋“ค ์ฃ„๋‹ค ์ฐข์–ด์„œ ๋ฒ„๋ ค๋ฒ„๋ฆฌ๊ณ  ๋ฐ”๋‹๋ผ์˜ ์Šคํ”ผ๋“œ๋ฅผ ๋А๊ปด๋ด์•ผ๊ฒ ๋‹ค.


๐Ÿ“ ๋งˆ๋ฌด๋ฆฌ ํ€ด์ฆˆ

Q1. ์˜์ฒ ์ด๊ฐ€ ๋‹ค์Œ์ฒ˜๋Ÿผ useCallback์„ ๋‚จ๋ฐœํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด ์ฝ”๋“œ๊ฐ€ ์ƒ์งœ๋กœ ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“œ๋Š” ์ฝ”๋“œ๋ณด๋‹ค ์„ฑ๋Šฅ์ด ์˜คํžˆ๋ ค '์•ˆ ์ข‹์€(์•…ํ™”๋œ)' ์ด์œ ๋ฅผ ๊ฐ€์žฅ ์ •ํ™•ํžˆ ์„ค๋ช…ํ•œ ๊ฒƒ์€?

function SimpleCard() {
  const [like, setLike] = useState(false);
 
  const toggleLike = useCallback(() => {
    setLike(!like);
  }, [like]);
 
  return <button onClick={toggleLike}>์ข‹์•„์š”</button>;
}
  • A) useCallback์€ ๋ธŒ๋ผ์šฐ์ €์˜ ์ „์—ญ GPU ๊ฐ€์†์„ ์“ธ๋ฐ์—†์ด ํ˜ธ์ถœํ•˜๊ธฐ ๋•Œ๋ฌธ.
  • B) ์ด ํ•จ์ˆ˜๋Š” [like] ์˜์กด์„ฑ์„ ๊ฐ€์ง€๊ณ  ์žˆ์œผ๋ฏ€๋กœ ์œ ์ €๊ฐ€ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅผ ๋•Œ๋งˆ๋‹ค(์ƒํƒœ๊ฐ€ ๋ณ€ํ•˜๋ฏ€๋กœ) ์–ด์ฐจํ”ผ ๊ณ„์† "ํ•จ์ˆ˜๋ฅผ ์ƒˆ๋กœ ์ƒ์„ฑ"ํ•ด์•ผ ํ•œ๋‹ค. ์ด๋•Œ ๊ธฐ์กด ํ•จ์ˆ˜ ์ƒ์„ฑ์„ ํŒŒ๊ดดํ•˜๋Š” ํผ(๊ธฐ๋ณธ ๋™์ž‘)์— ๋ง๋ถ™์—ฌ ์˜์กด์„ฑ ๋ฐฐ์—ด์„ ๋ฃจํ”„ ๋Œ๋ฉฐ ๋น„๊ตํ•˜๋Š” ์˜ค๋ฒ„ํ—ค๋“œ ์—ฐ์‚ฐ๊นŒ์ง€ ๋ค์œผ๋กœ ๋– ์•ˆ์•˜๊ธฐ ๋•Œ๋ฌธ.
  • C) useCallback์ด ๋ถˆ๋ฆฌ๋Š” ์ˆœ๊ฐ„ ์ž์‹ ์ปดํฌ๋„ŒํŠธ๋“ค์˜ ๋ชจ๋“  ๋ฆฌ๋ Œ๋”๋ง ํŒŒ์ดํ”„๋ผ์ธ์ด ์ •์ง€๋˜๊ธฐ ๋•Œ๋ฌธ.
  • D) useCallback ์•ˆ์—์„œ๋Š” ๋ถˆ๋ณ€์„ฑ ์œ„๋ฐฐ(!like)๊ฐ€ ๊ฐ์ง€๋˜์–ด ์—๋Ÿฌ๋ฅผ ๋ฑ‰๊ธฐ ๋•Œ๋ฌธ.

โœ… ์ •๋‹ต: B

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค: ์ตœ์ ํ™”์˜ ์—ญ์„ค์„ ์ฐŒ๋ฅด๋Š” ๋งˆ์Šคํ„ฐํ”ผ์Šค ์„ค๊ณ„ ๋ฌธ์ œ์ž…๋‹ˆ๋‹ค! ์˜์ฒ ์ด๋Š” ํ•จ์ˆ˜ ์ƒ์„ฑ์„ ๋ง‰๊ฒ ๋‹ต์‹œ๊ณ  ์ €๊ฑธ ์ผ์ง€๋งŒ, ๋ฒ„ํŠผ์„ ํด๋ฆญํ•  ๋•Œ๋งˆ๋‹ค like ๊ฐ’์ด ๋ฐ”๋€๋‹ˆ๋‹ค. ์˜์กด์„ฑ ๋ฐฐ์—ด [like]๊ฐ€ ๋ฐ”๋€Œ์—ˆ์œผ๋ฏ€๋กœ ๋ฆฌ์•กํŠธ๋Š” ์–ด์ฐจํ”ผ ๊ธฐ์กด ์บ์‹œ๋ฅผ ๋ฒ„๋ฆฌ๊ณ  ํ•จ์ˆ˜๋ฅผ ๋˜ ์ƒˆ๋กœ ๋งŒ๋“ญ๋‹ˆ๋‹ค(์บ์‹ฑ ์‹คํŒจ).
๊ฒฐ๊ตญ ์ด ์ง“์€ 1. ํ•จ์ˆ˜ ์ƒˆ๋กœ ๋งŒ๋“ค๊ธฐ + 2. ์ด์ „ like์™€ ์ง€๊ธˆ like ๋น„๊ตํ•˜๋Š” ์ถ”๊ฐ€ ์—ฐ์‚ฐํ•˜๊ธฐ ๋ผ๋Š” ๋”๋ธ” ํŽ˜๋„ํ‹ฐ๋ฅผ ๋ฌผ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์•„๋ฌด ์ด๋“ ์—†์ด ๋ฉ”๋ชจ๋ฆฌ์™€ CPU ์‹œ๊ฐ„๋งŒ ํŒŒ๋จน๋Š” ์ตœ์•…์˜ ํ–‰์œ„์ž…๋‹ˆ๋‹ค.

Q2. useMemo๋ฅผ ๋ฐ˜๋“œ์‹œ, ๋ฌด์กฐ๊ฑด์ ์œผ๋กœ ๋„์ž…ํ•ด์•ผ ํ•˜๋Š” ์œ ์ผํ•œ(๊ฐ€์žฅ ํ™•์‹คํ•œ) ํƒ€์ด๋ฐ์„ ๊ณจ๋ผ๋ณด์„ธ์š”.

  • A) ๋ฐฐ์—ด์˜ map ๋ฉ”์„œ๋“œ๋กœ UI ๋ฆฌ์ŠคํŠธ(<li/>)๋ฅผ 10์—ฌ ๊ฐœ ์ˆœํšŒํ•ด์„œ ๊ทธ๋ ค๋‚ผ ๋•Œ.
  • B) ๋ฐฑ์—”๋“œ์—์„œ ๋ฐ›์•„์˜จ ๊ฑฐ๋Œ€ํ•œ 10,000์ค„์˜ JSON ๋ฐฐ์—ด์„ ์žฌ๊ฐ€๊ณตํ•˜๊ณ  ํด๋ผ์ด์–ธํŠธ๋‹จ์—์„œ ์ •๋ ฌ(Sort) ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ํƒœ์šธ ๋•Œ.
  • C) <input> ์ฐฝ์—์„œ ์œ ์ €๊ฐ€ ํƒ€์ดํ•‘ํ•  ๋•Œ๋งˆ๋‹ค ๊ทธ state ๊ฐ’์„ ์ถ”์ ํ•ด์•ผ ํ•  ๋•Œ.
  • D) ์™ธ๋ถ€์˜ React Router ๊ฒฝ๋กœ(useLocation)์˜ ๋ณ€๊ฒฝ์„ ๊ฐ์ง€ํ•ด์•ผ ํ•  ๋•Œ.

โœ… ์ •๋‹ต: B

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค: B์ฒ˜๋Ÿผ ๋ช…๋ฐฑํ•˜๊ฒŒ ๊ณ ๋น„์šฉ ๋ณ‘๋ชฉ ์—ฐ์‚ฐ("Expensive Calculation")์„ ์ฒ˜๋ฆฌํ•  ๋•Œ๊ฐ€ ๋ฐ”๋กœ useMemo์˜ ์ง„์ •ํ•œ ๋ฌด๋Œ€์ž…๋‹ˆ๋‹ค. A์˜ ์ž์ž˜ํ•œ ๊ทธ๋ฆฌ๊ธฐ, C์˜ ๋‹จ์ˆœ ์ƒํƒœ ์ถ”์  ๋“ฑ์€ ๊ทธ๋ƒฅ ๋ Œ๋”๋ง ๋‹จ๊ณ„์—์„œ ๋‚ ๊ฒƒ์œผ๋กœ ๋˜์ ธ์ฃผ์„ธ์š”. ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋Š” ๋‹น์‹ ์ด ์ƒ๊ฐํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค ์ˆ˜๋ฐฑ ๋ฐฐ ๋น ๋ฆ…๋‹ˆ๋‹ค. ์“ธ๋ฐ์—†์ด ๋‹ค useMemo ์ฒ˜๋ฆฌํ•˜๋ฉด ๋ฉ”๋ชจ๋ฆฌ ๊ณต๊ฐ„ ๋ฆญ(Leak)๋งŒ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

Q3. ๋‹ค์Œ์˜ '์ตœ์ ํ™” ์‹คํŒจ' ์ผ€์ด์Šค๋ฅผ ๋ณด๊ณ , React.memo์™€ useCallback์€ ์™œ ์„œ๋กœ ๋–ผ๋ž˜์•ผ ๋—„ ์ˆ˜ ์—†๋Š” ์„ธํŠธ ๋ฌด๊ธฐ์ธ์ง€๋ฅผ ์„ค๋ช…ํ•ด๋ณด์„ธ์š”.

// 1. ์ž์‹: React.memo() ์—†์ด ๋‚ ๊ฒƒ์˜ ์ปดํฌ๋„ŒํŠธ
function ChildButton({ onClick }) {
  console.log("์ž์‹ ๋ฆฌ๋ Œ๋”๋ง ๋จ!");
  return <button onClick={onClick}>๋‚˜๋ฅผ ๋ˆŒ๋Ÿฌ์š”</button>;
}
 
// 2. ๋ถ€๋ชจ:
function Parent() {
  const [val, setVal] = useState('');
 
  // ์˜์ฒ : "์™€ํ•˜ํ•˜! useCallback์œผ๋กœ ํ•จ์ˆ˜ ์ฐธ์กฐ๊ฐ’์„ ์™„๋ฒฝํžˆ ๋™๊ฒฐ์‹œ์ผฐ์œผ๋‹ˆ,
  // ์ž…๋ ฅ์ฐฝ์— ํƒ€์ดํ•‘(val ๊ฐฑ์‹ )ํ•ด๋„ ์ž์‹์€ ์ ˆ๋Œ€ ๋ฆฌ๋ Œ๋”๋ง ์•ˆ ๋˜๊ฒ ์ง€?"
  const handleClick = useCallback(() => console.log('๋ˆŒ๋ ท๋”ฐ'), []);
 
  return (
    <div>
      <input value={val} onChange={e => setVal(e.target.value)} />
      <ChildButton onClick={handleClick} />
    </div>
  );
}

โœ… ์ •๋‹ต ๋ฐ ์ฃผ๊ด€์‹ ํ•ด์„ค:

์˜์ฒ ์ด์˜ ์˜ˆ์ƒ๊ณผ ๋‹ฌ๋ฆฌ, ์ธํ’‹ ์ฐฝ์— ๊ธ€์ž๋ฅผ ์น  ๋•Œ๋งˆ๋‹ค ์ฝ˜์†”์—๋Š” **"์ž์‹ ๋ฆฌ๋ Œ๋”๋ง ๋จ!"**์ด ๋ฏธ์นœ ๋“ฏ์ด ์ฐํž™๋‹ˆ๋‹ค.
์ด์œ (Why): ๋ฆฌ์•กํŠธ์˜ ๊ธฐ๋ณธ ๋ Œ๋”๋ง ๊ทœ์น™ 1์กฐ 1ํ•ญ, **"๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ(Parent)์˜ state๊ฐ€ ๋ฐ”๋€Œ์–ด ๋ Œ๋”๋ง์ด ๋Œ๋ฉด, ๊ทธ ์ž์‹(Child)๋“ค์€ ํ”„๋กญ์Šค๊ฐ€ ๋ณ€ํ–ˆ๋“  ์•ˆ ๋ณ€ํ–ˆ๋“  ๋ฌป์ง€๋„ ๋”ฐ์ง€์ง€๋„ ์•Š๊ณ  ๋ฌด์กฐ๊ฑด ์‹น ๋‹ค ๊ฐ•์ œ ๋ฆฌ๋ Œ๋”๋ง๋œ๋‹ค"**๋ผ๋Š” ๋Œ€์›์น™ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.
๋ถ€๋ชจ๊ฐ€ useCallback์œผ๋กœ ๊ธฐ๊ป onClick์˜ ์ฃผ์†Œ๊ฐ’(ํฌ์žฅ์ง€)์„ ๊ทธ๋Œ€๋กœ ์œ ์ง€ํ•ด์„œ ๋„˜๊ฒจ์คฌ๋”๋ผ๋„, ์ž์‹์ธ <ChildButton>์ด ๊ทธ ํฌ์žฅ์ง€๊ฐ€ ์˜ˆ์ „๊ณผ ๊ฐ™์€์ง€ ๋‹ค๋ฅธ์ง€ ๋™๋“ฑ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ๋ง‰์•„๋‚ด๋Š” ๋ฐฉํŒจ(React.memo)๋ฅผ ์ž…๊ณ  ์žˆ์ง€ ์•Š๋‹ค๋ฉด, ๊ทธ๋ƒฅ ๋ฌด์ง€์„ฑ์œผ๋กœ ์“ธ๋ ค ๋‚ด๋ ค๊ฐ€ ์ƒˆ๋กœ ๋ Œ๋”๋ง์„ ๋œ๋‹ˆ๋‹ค!
๋”ฐ๋ผ์„œ ๋ถ€๋ชจ์˜ useCallback์€ ๋ฐ˜๋“œ์‹œ ์ž์‹์˜ React.memo๋ผ๋Š” ๋ฐฉ์–ด๋ง‰ ์œ„์ ฏ๊ณผ ์Œ์œผ๋กœ ๊ฒฐํ•ฉ๋˜์—ˆ์„ ๋•Œ๋งŒ ๋น„๋กœ์†Œ '๋ Œ๋”๋ง ์ตœ์ ํ™” ์ฝค๋ณด'๋กœ์„œ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. ๋ฐ˜์ชฝ์งœ๋ฆฌ ์ตœ์ ํ™”๋Š” ๊ฑฐ๋Œ€ํ•œ ํ•จ์ •์ผ ๋ฟ์ž…๋‹ˆ๋‹ค.