๐Ÿ’ก 10. ์ง„์ •ํ•œ ์ตœ์ ํ™”: ์•„ํ‚คํ…์ฒ˜์™€ Children ํŒจํ„ด

๐Ÿ“‹ ๊ฐœ์š”

useMemo ๋‚จ๋ฐœ ๋Œ€์‹ , ๋ฆฌ์•กํŠธ์˜ ๋ณธ์งˆ์ธ Children(ํ•ฉ์„ฑ)๊ณผ ์ƒํƒœ ๊ฒฉ๋ฆฌ(Colocation)๋ฅผ ์ด์šฉํ•ด ๋ Œ๋”๋ง ํญํฌ์ˆ˜๋ฅผ ๊ทผ๋ณธ์ ์œผ๋กœ ๋Š์–ด๋‚ด๋Š” ํ”„๋ก ํŠธ์—”๋“œ ์ตœ์ ํ™”์˜ ๊ทน์˜๋ฅผ ๋ฐฐ์›๋‹ˆ๋‹ค.

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

  • ์ตœ์ƒ๋‹จ ์ปดํฌ๋„ŒํŠธ์— ์ƒํƒœ๊ฐ€ ์žˆ์„ ๋•Œ ๋ฐœ์ƒํ•˜๋Š” ๋ฌด์˜๋ฏธํ•œ ๋ Œ๋”๋ง์„ ๊ตฌ์กฐ์ ์œผ๋กœ ์ฐข์–ด์„œ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค.
  • React.memo ๋”ฐ์œ„ ์—†์ด๋„, children prop ํ•ฉ์„ฑ(Composition)๋งŒ์œผ๋กœ ๋งˆ๋ฒ•์ฒ˜๋Ÿผ ๋ Œ๋”๋ง์„ ๋ง‰์•„๋‚ด๋Š” ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ํ„ฐ๋“ํ•œ๋‹ค.
  • ์ƒํƒœ ๊ด€๋ฆฌ ๋„๊ตฌ๋‚˜ ํ›…(Hooks)์— ๊ธฐ๋Œ€๊ธฐ ์ „์—, "๋ฐ•์Šค(Component)๋ฅผ ์–ด๋–ป๊ฒŒ ์ชผ๊ฐค ๊ฒƒ์ธ๊ฐ€" ๋ถ€ํ„ฐ ๊ณ ๋ฏผํ•˜๋Š” ์ง„์งœ ์‹œ๋‹ˆ์–ด๋กœ ์ง„ํ™”ํ•œ๋‹ค.

๐Ÿ“‹ ๋ชฉ์ฐจ


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

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

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

  • ์˜์ฒ (์‹ ์ž…): "์„ ๋ฐฐ๋‹˜, ํ™”๋ฉด ๊ตฌ์„์— ์žˆ๋Š” ์ƒ‰์ƒํ‘œ(Color Picker) ํ•˜๋‚˜ ์›€์ง์˜€๋Š”๋ฐ ์™œ ์ค‘์•™์— ์žˆ๋Š” ์ˆ˜๋ฐฑ ๊ฐœ์˜ ํ‘œ(Table) ์…€๋“ค์ด ์‹น ๋‹ค ๋ฆฌ๋ Œ๋”๋ง๋˜๋‚˜์š”? React.memo๋กœ ํ‘œ ์…€ 1000๊ฐœ๋ฅผ ๋‹ค ๊ฐ์‹ธ๋ฒ„๋ฆฌ๋ฉด ๋ ๊นŒ์š”?"
  • ์˜ํ˜ธ(๋ฆฌ๋“œ): "์˜์ฒ  ๋‹˜. ์ฝ”๋“œ๋ฅผ ๋•์ง€๋•์ง€ ๊ธฐ์›Œ ๋ถ™์ด๋ ค๋Š” ์ƒ๊ฐ์„ ๋ฒ„๋ฆฌ์„ธ์š”. ์ด๊ฑด ์ฝ”๋”ฉ(Hooks)์˜ ๋ฌธ์ œ๊ฐ€ ์•„๋‹ˆ๋ผ ๊ฑด์ถ•๋ฌผ ๋ผˆ๋Œ€(Architecture)์˜ ๋ฌธ์ œ์ž…๋‹ˆ๋‹ค. ํ‘œ๋ฅผ ๊ฐ์‹ธ๋Š” ๋ฒฝ์„ ํ—ˆ๋ฌผ๊ณ  ์žฌ๋ฐฐ์น˜ํ•ด๋ณด์‹œ์ฃ ."

๐Ÿค” ์™œ ์•Œ์•„์•ผ ํ•˜๋Š”๊ฐ€: ๊ฐˆ๋ผํŒŒ๊ณ ์Šค์—์„œ ๋ฒ—์–ด๋‚˜๊ธฐ

๊ฐ€์žฅ ์•ˆํƒ€๊นŒ์šด ์ƒํ™ฉ์€, ์ดˆ๋ณด์ž๋“ค์ด ์ƒํƒœ๋ฅผ ๋Œ์–ด์˜ฌ๋ ค ๋†“๊ณ  ๋ Œ๋” ๋ ‰(Jank)์ด ๋ฐœ์ƒํ•˜๋ฉด ๊ณง์žฅ useMemo, useCallback, React.memo๋ฅผ ์ˆ˜์‹ญ ๊ฐœ ๋ณต๋ถ™ํ•˜๊ธฐ ์‹œ์ž‘ํ•œ๋‹ค๋Š” ๊ฒ๋‹ˆ๋‹ค.

๐Ÿค” ์ž ๊น, ๋จผ์ € ์ƒ๊ฐํ•ด๋ด
์˜์ฒ ์ด๊ฐ€ ์ง  ์ด ๋ฌด๊ฑฐ์šด ๋Œ€์‹œ๋ณด๋“œ. ์ƒ๋‹จ์˜ ์•„์ฃผ ๊ฐ€๋ฒผ์šด ์ƒ‰์ƒ(ํ…Œ๋งˆ)๋งŒ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ฐ”๊พธ๊ณ  ์‹ถ์€๋ฐ... ์™œ ์ € ์•„๋ž˜ ๋ฌด๊ฑฐ์šด ์ฐจํŠธ๊นŒ์ง€ ๋‹ค ์žฌ๊ณ„์‚ฐ๋˜๋Š” ๋Œ€์ฐธ์‚ฌ๊ฐ€ ํ„ฐ์งˆ๊นŒ?

// โŒ ์˜์ฒ ์ด์˜ ํญํƒ„ ์•„ํ‚คํ…์ฒ˜ (State in root)
function BigDashboard() {
  // ์‚ฌ์šฉ์ž๊ฐ€ ์ปฌ๋Ÿฌ ์Šฌ๋ผ์ด๋”๋ฅผ ์ซ™- ๋“œ๋ž˜๊ทธํ•˜๋ฉด ์ด ์ƒํƒœ๊ฐ€ ์ดˆ๋‹น 60ํ”„๋ ˆ์ž„์œผ๋กœ ๋ฐ”๋€๋‹ค!
  const [color, setColor] = useState("red");
 
  return (
    <div style={{ background: color }}>
      <input type="color" onChange={(e) => setColor(e.target.value)} />
      
      {/* ๐Ÿ’ฅ ์ด ๋…€์„๋“ค์€ ์ƒ‰์ƒ๊ณผ 1๋„ ๊ด€๋ จ์ด ์—†์ง€๋งŒ ๋ถ€๋ชจ๊ฐ€ ๋ Œ๋”๋ง๋˜๋‹ˆ๊นŒ ๊ฐ•์ œ๋กœ ๊ฐˆ๋ ค ๋‚˜๊ฐ„๋‹ค */}
      <VeryHeavyChartA />
      <VeryHeavyChartB />
      <MonsterGridTable />
    </div>
  );
}

์ด๊ฑธ ๊ณ ์น˜๋ผ๊ณ  ํ•˜๋ฉด 99%์˜ ์ฃผ๋‹ˆ์–ด๋“ค์€ ํ•˜์œ„ ์ปดํฌ๋„ŒํŠธ 3๊ฐœ๋ฅผ ์ „๋ถ€ React.memo ๋กœ ๊ฐ์‹ธ๋ ค๊ณ  ๋“ญ๋‹ˆ๋‹ค. ์˜ํ˜ธ(5๋…„ ์ฐจ ์‹œ๋‹ˆ์–ด)๋Š” ๊ทธ๋ ‡๊ฒŒ ํ’€์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์˜ํ˜ธ์˜ ๋ฌด๊ธฐ๋Š” ์˜ค์ง "๊ตฌ์กฐ(Component Split)" ์ž…๋‹ˆ๋‹ค.


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

๐Ÿง’ 5์‚ด์—๊ฒŒ ์„ค๋ช…ํ•œ๋‹ค๋ฉด?

๋ฐฉ๋ฒ• 1 (๋ Œ๋” ์ตœ์ ํ™” ํ›… ๋ฉ์–ด๋ฆฌ - ์˜์ฒ ):
๊ต์žฅ ์„ ์ƒ๋‹˜(๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ)์ด ๋งˆ์ดํฌ๋กœ ์ž”์†Œ๋ฆฌ(์ƒํƒœ ๋ณ€๊ฒฝ)๋ฅผ ์‹œ์ž‘ํ–ˆ์–ด!
๊ทธ ์†Œ๋ฆฌ๋ฅผ ๋‹ค ๋“ค์–ด์•ผ ํ•ด์„œ ๊ฐ•๋‹น์˜ 1ํ•™๋…„๋ถ€ํ„ฐ 6ํ•™๋…„ ํ•™์ƒ ์ „์ฒด๊ฐ€ ๊ท€๋ฅผ ๋ง‰๊ณ  ๊ดด๋กญ๊ฒŒ ๋ชธ์„ ๋น„ํ‹€์ง€(๋ฆฌ๋ Œ๋”๋ง).
์˜์ฒ ์ด๋Š” ์ด๊ฑธ ๋ง‰์•„๋ณด๊ฒ ๋‹ค๊ณ  ํ•™์ƒ 1000๋ช…์—๊ฒŒ ์ผ์ผ์ด ๋ฐฉ์Œ ํ—ค๋“œํฐ(React.memo) ์„ ์‚ฌ์„œ ๊ฐ•์ œ๋กœ ์”Œ์šฐ๊ณ  ์žˆ์–ด. ๋ˆ๊ณผ ๋…ธ๋ ฅ์ด ์—„์ฒญ ๊นจ์ง€๊ฒ ์ง€?

๋ฐฉ๋ฒ• 2 (์•„ํ‚คํ…์ฒ˜ ๊ตฌ์กฐ์˜ ๋งˆ๋ฒ• - ์˜ํ˜ธ):
์˜ํ˜ธ๋Š” ํ•™์ƒ๋“ค์—๊ฒŒ ํ—ค๋“œํฐ์„ ์”Œ์šธ ์ƒ๊ฐ์กฐ์ฐจ ์•ˆ ํ•ด. ๋Œ€์‹  ๊ต์žฅ ์„ ์ƒ๋‹˜์„ ์ž‘๊ณ  ์ข์€ ๋ฐฉ(๊ณ ๋ฆฝ๋œ ์ž์‹ ์ปดํฌ๋„ŒํŠธ) ์•ˆ์œผ๋กœ ์ง‘์–ด๋„ฃ์€ ๋‹ค์Œ ๋ฌธ์„ ์พ… ๋‹ซ์•„๋ฒ„๋ ค!(์ƒํƒœ ๊ฒฉ๋ฆฌ / Colocation)
๊ต์žฅ ์„ ์ƒ๋‹˜์ด ๋ฐฉ ์•ˆ์—์„œ ํ˜ผ์ž ์ถค์ถ”๊ณ  ๊ฝน๊ณผ๋ฆฌ๋ฅผ ์ณ๋„(์ƒํƒœ ๋ณ€๊ฒฝ), ๊ฐ•๋‹น์— ์žˆ๋Š” ํ•™์ƒ ์ปดํฌ๋„ŒํŠธ๋“ค์€ ๋ฌด์Šจ ์ผ์ด ์ผ์–ด๋‚˜๋Š”์ง€๋„ ๋ชจ๋ฅธ ์ฑ„ ํ‰์˜จํ•˜๊ฒŒ ๋†€์•„. (๋ฌด๊ฒฐ์  ๋ Œ๋”๋ง ๋ฐฉ์–ด ์„ฑ๊ณต)

์ด๊ฒŒ ๋ฐ”๋กœ "์ƒํƒœ ๋ฐ€์–ด ๋‚ด๋ฆฌ๊ธฐ(State Push-down)" ์™€ "์ปดํฌ๋„ŒํŠธ ํ•ฉ์„ฑ(Composition)" ์˜ ์œ„๋ ฅ์ž…๋‹ˆ๋‹ค.


๐Ÿงฉ ๊ทน์˜ ์ฒด๋“: ์ง„์ •ํ•œ ์ตœ์ ํ™” 2๋Œ€ ๋ฐฉ์–ด๋ง‰

โœ… ๋ฐฉ์–ด์ˆ  1. ์ƒํƒœ๊ฐ€ ๋ณ€ํ•˜๋Š” ๋ฒ”์ธ์„ ์ง€ํ•˜ ๊ฐ์˜ฅ์— ๊ฐ€๋‘๊ธฐ (State Push-Down)

๋ฌด๊ฑฐ์šด ์ฐจํŠธ(Heavy)๋“ค์€ ์• ์ดˆ์— ์ € color ์ƒํƒœ ๋ณ€์ˆ˜์™€ ์—ฎ์ผ ์ด์œ ๊ฐ€ ๋‹จ 1%๋„ ์—†๋Š” ๋‚จ๋‚จ์ž…๋‹ˆ๋‹ค.
์ด๋Ÿด ๋•, ์ƒํƒœ๋ฅผ ์‚ฌ์šฉ(์†Œ๋น„)ํ•˜๋Š” ๋…€์„๋“ค๋งŒ ๋”ฑ ํ•€์…‹์œผ๋กœ ์ง‘์–ด์„œ ๋ณ„๋„์˜ ์ปดํฌ๋„ŒํŠธ๋กœ ๋นผ๋‚ธ ๋‹ค์Œ ๊ทธ ์•ˆ์œผ๋กœ ์ƒํƒœ๋ฅผ ๋˜์ ธ๋ฒ„๋ฆฝ๋‹ˆ๋‹ค(๊ณ ๋ฆฝ).

// โœ… ๋ฆฌํŒฉํ† ๋ง 1. color ์ƒํƒœ๋ฅผ ๊ฒฉ๋ฆฌ์‹œํ‚จ ๋ฐฉ์–ด๋ง‰ ์ปดํฌ๋„ŒํŠธ ์ œ์ž‘
function ColorPickerWrapper() {
  const [color, setColor] = useState("red");
  
  // ๐ŸŽฏ ์ด ์ปดํฌ๋„ŒํŠธ๋Š” ์˜ค์ง ์ž์‹ ๋งŒ ๋ถ€์ˆ˜๊ณ  ๋ฆฌ๋ Œ๋”๋ง๋œ๋‹ค. ์˜†์ง‘ ๋Œ€์‹œ๋ณด๋“œ์—” ํ”ผํ•ด๋ฅผ ์•ˆ ์คŒ!
  return (
    <div style={{ background: color }}>
      <input type="color" onChange={(e) => setColor(e.target.value)} />
    </div>
  );
}
 
// ๋ถ€๋ชจ: ํ‰-์˜จ. ํ•œ ๋ฒˆ ๋ Œ๋”๋ง๋˜๊ณ  ๋‚˜๋ฉด ์˜์›ํžˆ ๋‹ค์‹œ ๋ถˆ๋ฆฌ์ง€ ์•Š๋Š”๋‹ค.
function BigDashboard() {
  return (
    <>
      <ColorPickerWrapper /> 
      {/* ์ด์ œ๋ถ€ํ„ฐ ์–˜๋„ค๋“ค์€ ๊ต์žฅ์„ ์ƒ๋‹˜ ๋ฐฉ๊ณผ ๋ถ„๋ฆฌ๋˜์–ด ์™„์ „ํžˆ ์•ˆ์ „ํ•จ */}
      <VeryHeavyChartA />
      <VeryHeavyChartB />
    </>
  );
}

๊ฒฐ๊ณผ: ๋‹จ 1๊ฐœ์˜ useMemo, React.memo ์—†์ด ๋ฌด๊ฑฐ์šด ์ปดํฌ๋„ŒํŠธ๋“ค์˜ ํญํฌ์ˆ˜ ๋ Œ๋”๋ง์„ 100% ๋Š์–ด๋ƒˆ์Šต๋‹ˆ๋‹ค.


โœ… ๋ฐฉ์–ด์ˆ  2. ์ž์‹(Children)์—๊ฒŒ ์ง ๋– ์•ˆ๊ธฐ (Composition)

์œ„์˜ ์˜ˆ์‹œ๋Š” ๊น”๋”ํ•˜๊ฒŒ ์˜†์œผ๋กœ ์น˜์› ์œผ๋‹ˆ๊นŒ ์‰ฌ์› ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ๋งŒ์•ฝ ์ € ์ƒ‰๊น” ๊ป๋ฐ๊ธฐ <div style={{background: color}}> ์•ˆ์— ๋ฐ˜๋“œ์‹œ ์ € ํ—ค๋น„ํ•œ ์ฐจํŠธ๋“ค์„ ์ž์‹ ์š”์†Œ๋กœ ์ง์ ‘ ํ’ˆ๊ณ  ํ’ˆ์–ด์ ธ์•ผ ๋งˆํฌ์—…(UI ๋ ˆ์ด์•„์›ƒ) ๊ตฌ์กฐ ๋ผ๋ฉด ์–ด๋–จ๊นŒ์š”? ๋ชป ๋„๋ง๊ฐ‘๋‹ˆ๋‹ค!

// "๋„๋ง์น  ์ˆ˜ ์—†๋‹ค. ๋‚ด ํ’ˆ ์•ˆ์— ๋ฌด๊ฑฐ์šด ์• ๋“ค์„ ๊ทธ๋ ค์•ผ๋งŒ ํ•œ๋‹ค."
function ThemeApp() {
  const [color, setColor] = useState("red");
  return (
    <div style={{ background: color }} className="layout-box">
      <input type="color" onChange={(e) => setColor(e.target.value)} />
      {/* ๐Ÿ’ฅ ๋ ˆ์ด์•„์›ƒ์ƒ ์ด ๋ฐ•์Šค ์•ˆ์— ๋“ค์–ด๊ฐ€์•ผ ํ•˜๋‹ˆ ๊ฐ™์ด ๋ Œ๋”๋ง ์ง€์˜ฅ์— ๋น ์ง! */}
      <VeryHeavyChartA /> 
    </div>
  );
}

์ด๋Ÿด ๋•Œ ์“ฐ๋Š” ํ”„๋ก ํŠธ์—”๋“œ ์ตœํ›„์˜ ๋งˆ๋ฒ• ์ฃผ๋ฌธ์ด ๋ฐ”๋กœ ๊ตฌ๋ฉ ๋šซ๊ธฐ, {children} ๊ตฌ๋ฉ ๋‚ด๊ธฐ ์ž…๋‹ˆ๋‹ค.

์œ„์˜ ๋ฒ”์ธ ๊ป๋ฐ๊ธฐ(ThemeApp)๋ฅผ, ์†์ด ํ…… ๋นˆ **์žฌํ‚ท(๊ป๋ฐ๊ธฐ ์ปดํฌ๋„ŒํŠธ)**์œผ๋กœ ๋งŒ๋“ค์–ด์„œ ๋ถ„๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

// โœ… ๋ฆฌํŒฉํ† ๋ง 2-1. ์žฌํ‚ท ์ปดํฌ๋„ŒํŠธ (์ƒํƒœ๋Š” ์—ฌ๊ธฐ ๊ฐ‡ํž˜)
function ColorBoxJacket({ children }) {
  const [color, setColor] = useState("red");
  return (
    <div style={{ background: color }} className="layout-box">
      <input type="color" onChange={(e) => setColor(e.target.value)} />
      {/* ๐ŸŽฏ ๋ฆฌ์•กํŠธ ๋งˆ๋ฒ•: ์ด ์ž์‹์€ ๋ถ€๋ชจ(ColorBoxJacket)๊ฐ€ ๋ฆฌ๋ Œ๋”๋ง๋˜์–ด๋„, 
          ํฌ์žฅ์ง€(App)์—์„œ ๋ฏธ๋ฆฌ ๋นš์–ด์ค€ '๋™์ผํ•œ ๋Œ€์ƒ'์ด๋ฏ€๋กœ ๋ฐฉํŒจ๋ฅผ ์–ป๋Š”๋‹ค! */}
      {children}
    </div>
  );
}
 
// โœ… ๋ฆฌํŒฉํ† ๋ง 2-2. ์ตœ์ข… ์กฐ๋ฆฝ ์„ค๋ช…์„œ ํ™”๋ฉด (์ƒํƒœ๋ฅผ ๋ชจ๋ฅด๋Š” ์ฒญ์ •๊ตฌ์—ญ)
function App() {
  return (
    <ColorBoxJacket>
      {/* ๐ŸŽฏ ์ด ๋ฌด๊ฑฐ์šด ์ฐจํŠธ๋Š” App์ด ๊ทธ๋ฆฐ๋‹ค! App์€ Color ์ƒํƒœ๋ฅผ ๋ชจ๋ฅด๋‹ˆ 
          ๋ฆฌ๋ Œ๋”๋ง๋˜์ง€ ์•Š๊ณ , ๋”ฐ๋ผ์„œ ์ด ๋…€์„๋„ ์˜์›ํžˆ ํ•œ ๋ฒˆ๋งŒ ๊ทธ๋ ค์ ธ ์ „๋‹ฌ๋œ๋‹ค! */}
      <VeryHeavyChartA />
    </ColorBoxJacket>
  );
}

์ด ๊ตฌ์กฐ๋Š” ๋งˆ๋ฒ•๊ณผ๋„ ๊ฐ™์Šต๋‹ˆ๋‹ค. ์Šฌ๋ผ์ด๋”๋ฅผ ์ญ‰ ๋Œ๋ฉด ColorBoxJacket ์ปดํฌ๋„ŒํŠธ๋Š” ์ดˆ๋‹น 60๋ฒˆ์”ฉ ๋น„๋ช…์„ ์ง€๋ฅด๋ฉฐ ๋ฆฌ๋ Œ๋”๋ง(๋ฐฐ๊ฒฝ์ƒ‰ ๊ต์ฒด)๋˜์ง€๋งŒ, ๊ทธ ์ž…๊ตฌ(children)๋ฅผ ํ†ตํ•ด ์™ ๊ฝ‚ํ˜€ ๋Œ€๊ธฐํ•˜๊ณ  ์žˆ๋Š” <VeryHeavyChartA /> ๋Š” ํ„ธ๋ ํ•˜๋‚˜ ๋ Œ๋”๋ง๋˜์ง€ ์•Š๊ณ  ๊ตณ๊ฑดํžˆ ์ œ์•ฝ ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์ด React.memo ์—†์ด ๊ตฌ์กฐ์  ํ•ฉ์„ฑ(Composition)๋งŒ์œผ๋กœ ์ด๋ฃจ์–ด๋‚ด๋Š” ์ตœ์ƒ์˜, ๋Œ€๊ธฐ์—…๊ธ‰ ์‹œ๋‹ˆ์–ด ์ตœ์ ํ™”์ž…๋‹ˆ๋‹ค.


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

์ตœ์ ํ™” ์ƒํ™ฉโŒ ์ดˆ๋ณด์˜ ๋ฌด๊ธฐ (ํ›„์ฒ˜๋ฆฌ)โœ… ์‹œ๋‹ˆ์–ด์˜ ๋ฌด๊ธฐ (์„ค๊ณ„, ์•„ํ‚คํ…์ฒ˜)
์ƒํƒœ ํ•˜๋‚˜๊ฐ€ ์ผ๋ถ€ ํ˜•์ œ๋“ค์„ ํญ์‚ฌ์‹œํ‚ฌ ๋•Œ๋ฉ€์ฉกํ•œ ํ˜•์ œ๋“ค์„ ์–ต์ง€๋กœ React.memo์— ์ง‘์–ด๋„ฃ์Œ์ƒํƒœ ๋ณ€ํ™”์˜ ๋ฒ”์ธ ๋ถ€๋ถ„๋งŒ ๋ถ„๋ฆฌ ์ปดํฌ๋„ŒํŠธ๋กœ ๋–ผ์–ด์„œ ๊ณ ๋ฆฝ์‹œํ‚ด
๊ป๋ฐ๊ธฐ ์ƒํƒœ ๋ณ€ํ™”๊ฐ€ ๋‚ด๋ถ€ ์•Œ๋งน์ด๋ฅผ ์ฃฝ์ผ ๋•Œ์•Œ๋งน์ด์— ๋ฐœ์•…ํ•˜๋ฉฐ useMemo/memo ๋–ก์น  ์‹œ๋„๋ฒ”์ธ ๊ป๋ฐ๊ธฐ๋ฅผ children prop ๊ตฌ์กฐ๋กœ ๊ตฌ๋ฉ ๋‚ด๊ณ  ๋ฐ–์—์„œ ์•Œ๋งน์ด๋ฅผ ๊ฝ‚์Œ

๐Ÿ’ก ํ•œ ์ค„๋กœ ๊ธฐ์–ตํ•˜๊ธฐ
"Hooks๋Š” ๋งŒ๋ณ‘ํ†ต์น˜์•ฝ์ด ์•„๋‹ˆ๋‹ค." ๋ Œ๋”๋ง ๋ณ‘๋ชฉ์ด ์ƒ๊ฒผ์„ ๋•Œ, ์ฒซ ๋ฒˆ์งธ๋กœ ์˜์‹ฌํ•  ๊ฒƒ์€ ์ฝ”๋”ฉ ๊ธฐ์ˆ ์ด ์•„๋‹ˆ๋ผ ๋‹น์‹ ์ด ์ปดํฌ๋„ŒํŠธ๋ฅผ ์กฐ๋ฆฝํ•œ ๋ผˆ๋Œ€(Tree)์˜ ๊ตฌ์กฐ ์ž…๋‹ˆ๋‹ค. ์ปดํฌ๋„ŒํŠธ์˜ ์ฑ…์ž„์„ ์ชผ๊ฐœ๋ผ(Split). ์•Œ๋งน์ด๊ฐ€ ๊ผฌ์˜€๋‹ค๋ฉด ํ•ฉ์„ฑ(Composition)ํ•ด๋ผ.


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

์—ฌํƒœ ๋‚œ React.memo๋กœ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์–ต์ง€๋กœ ํ‹€์–ด๋ง‰๋Š” ๊ฒŒ ์ตœ๊ณ ์˜ ๊ธฐ์ˆ ์ธ ์ค„ ์•Œ์•˜๋‹ค. ์˜ํ˜ธ ์„ ๋ฐฐ๊ฐ€ children ๊ตฌ๋ฉ ํ•˜๋‚˜๋กœ ๋ฌด๊ฑฐ์šด ์ฐจํŠธ ๋ Œ๋”๋ง์„ ์™„์ „ํžˆ ๋Š์–ด๋ƒˆ์„ ๋• ์ง„์งœ ์†Œ๋ฆ„์ด ๋‹์•˜๋‹ค.

๐Ÿ’ก "์ƒํƒœ๊ฐ€ ์š”๋™์น˜๋Š” ๋ฒ”์ธ์„ ์ง€ํ•˜ ๊ฐ์˜ฅ์— ๊ฐ€๋‘๊ฑฐ๋‚˜, children์œผ๋กœ ํ•ฉ์„ฑ์„ ํ•ด๋ฒ„๋ฆฌ๋ฉด ์–ต์ง€ ํ›…(Hook) ๋–ก์น  ์—†์ด ๊ฐ€์žฅ ์ˆœ์ˆ˜ํ•˜๊ฒŒ ์„ฑ๋Šฅ์„ ๋Œ์–ด์˜ฌ๋ฆด ์ˆ˜ ์žˆ๋‹ค."

Hook์˜ ํ˜„๋ž€ํ•จ๋ณด๋‹ค ์ปดํฌ๋„ŒํŠธ์˜ ์ง‘ ๋ผˆ๋Œ€, ์•„ํ‚คํ…์ฒ˜ ์ž์ฒด๊ฐ€ ํ›จ์”ฌ ์šฐ์›”ํ•œ ํ•ด๊ฒฐ์ฑ…์ด๋ž€ ๊ฑธ ๊นจ๋‹ฌ์•˜๋‹ค. ์˜ค๋Š˜ ๋ฐฐ์šด '์ƒํƒœ ๋ฐ€์–ด ๋‚ด๋ฆฌ๊ธฐ'๋ž‘ 'Children ํ†ต๊ณผ์‹œํ‚ค๊ธฐ' ๋ฐฉ์–ด์ˆ ๋งŒ ์จ๋จน์–ด๋„ ์ด์ œ ์–ด๋”” ๊ฐ€์„œ ๋ Œ๋” ํญ์ฃฝ ํ„ฐ๋œจ๋ ธ๋‹ค๊ณ  ์š•๋จน์„ ์ผ์€ ์—†๊ฒ ์ง€. ํ‡ด๊ทผ ํ›„ ์น˜ํ‚จ ์‹œ์ผœ๋จน์œผ๋ฉฐ ๋ฆฌ์•กํŠธ ๋ฌธ์„œ ํ•ฉ์„ฑ(Composition) ํŒŒํŠธ๋‚˜ ํ•œ ๋ฒˆ ๋” ์ •๋…ํ•ด์•ผ๊ฒ ๋‹ค.


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

Q1. ๋‹ค์Œ ์ค‘ ๊ณผ๋„ํ•œ ๋ Œ๋”๋ง์„ ๋ง‰๊ธฐ ์œ„ํ•ด 5๋…„ ์ฐจ ํ”„๋ก ํŠธ์—”๋“œ ๋ฆฌ๋“œ๊ฐ€ ๊ฐ€์žฅ ์šฐ์„ ์ ์œผ๋กœ ๊ณ ๋ ค(First Choice)ํ•ด์•ผ ํ•  ํ•ด๊ฒฐ ๋ฐฉ์‹์€ ๋ฌด์—‡์ธ๊ฐ€์š”?

  • A) ์ปดํฌ๋„ŒํŠธ ํ•˜๋‹จ์˜ ๋ชจ๋“  ์ง€์—ญ ํ•จ์ˆ˜๋“ค์„ ๋ชจ์กฐ๋ฆฌ useCallback ์ฝœ๋ฐฑ์œผ๋กœ ๊ฐ์‹ธ ๋‘”๋‹ค.
  • B) ๋ถˆํ•„์š”ํ•˜๊ฒŒ ๋†’์€ ๊ณณ(์ตœ์ƒ๋‹จ)์— ์„ ์–ธ๋œ ์ƒํƒœ(State)๊ฐ€ ์žˆ๋‹ค๋ฉด, ๊ทธ ์ƒํƒœ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฐ€์žฅ ๊นŠ์€ ๋ง๋‹จ ๊ณ„์ธต(Leaf Tree)์œผ๋กœ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ถ„๋ฆฌํ•ด ๋ฐ€์–ด ๋‚ด๋ฆฐ๋‹ค(State Colocation).
  • C) ๋ฌด๊ฑฐ์šด ์ปดํฌ๋„ŒํŠธ ํŒŒ์ผ ์ƒ๋‹จ์— ์ผ๋‹จ ๋ฌด์ง€์„ฑ์œผ๋กœ export default React.memo(MyComponent)๋ฅผ ๋ฐ•์•„๋†“๊ณ  ์‹œ์ž‘ํ•œ๋‹ค.
  • D) useEffect ์•ˆ์— ๋ฌด๊ฑฐ์šด DOM ์—…๋ฐ์ดํŠธ ๋กœ์ง์„ ๋ชฝ๋•… ์ง‘์–ด๋„ฃ๊ณ  setTimeout์œผ๋กœ ์ฒ˜๋ฆฌํ•œ๋‹ค.

โœ… ์ •๋‹ต: B

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค: ์„ฑ๋Šฅ ์ตœ์ ํ™”์˜ ์ œ1์›์น™์€ "ํ›…(Hooks)์— ๊ธฐ๋Œ€๊ธฐ ์ „์— ์ปดํฌ๋„ŒํŠธ ๋ชจ๋ธ(์„ค๊ณ„)์„ ๊ณ ์ณ๋ผ" ์ž…๋‹ˆ๋‹ค. A์™€ C๋Š” ๋ถ€๊ฐ€์ ์ธ ์˜ค๋ฒ„ํ—ค๋“œ(์บ์‹œ ๊ธฐ๋ก, props ์ผ์น˜๋„ ํŒ๋ณ„๋ฃจํ”„ ์—ฐ์‚ฐ)๋ฅผ ์œ ๋ฐœํ•˜๋Š” ๊ทน์•ฝ์ฒ˜๋ฐฉ์ผ ๋ฟ์ž…๋‹ˆ๋‹ค. ์ƒํƒœ๋ฅผ ๊ณ ๋ฆฝ์‹œํ‚ค๋Š” ๋ฐฉ๋ฒ•(Push State Down)์€ ๋ฆฌ์•กํŠธ ์—”์ง„ ์ž…์žฅ์—์„œ ์ถ”๊ฐ€์ ์ธ ํŒ๋ณ„์—ฐ์‚ฐ์„ 0ํšŒ๋กœ ๋งŒ๋“ค๋ฉฐ ๋ Œ๋”๋ง ํญํฌ์ˆ˜๋งŒ ๊ตฌ์กฐ๋ฌผ๋กœ ๋ฐฉ์–ดํ•ด๋ฒ„๋ฆฌ๋Š”, ๊ฐ€์žฅ ์™„๋ฒฝํ•˜๊ณ  ๊ณต์งœ์ธ ์ตœ์ ํ™” ๋น„๋ฒ•์ž…๋‹ˆ๋‹ค.

Q2. ์ฒ ์ˆ˜๊ฐ€ React.memo ์—†์ด children prop(ํ•ฉ์„ฑ ๊ธฐ์ˆ )์„ ์ด์šฉํ•ด ๋ Œ๋”๋ง ๋ฐฉ์–ด๋ฅผ ์งœ๋ณด์•˜์Šต๋‹ˆ๋‹ค. ์œ ์ €๊ฐ€ ์Šคํฌ๋กค์„ ๋‚ด๋ ค ํ—ค๋” ํˆฌ๋ช…๋„(opacity State) ๊ฐฑ์‹ ์ด ์ผ์–ด๋‚  ๋•Œ, ์™œ <HeavyContent />๋Š” ์žฌ๋ Œ๋”๋ง ๋ฐ๋ฏธ์ง€๋ฅผ 1๋„ ์•ˆ ๋ฐ›๊ณ  ์‚ด์•„๋‚จ์„ ์ˆ˜ ์žˆ์—ˆ์„๊นŒ์š”?

function ScrollAlphaHeader({ children }) {
  const [opacity, setOpacity] = useState(1);
  // .. (์Šคํฌ๋กค ์‹œ setState ์—ฐ๋ฐœ ๋ฐœ๋™)
  return <div style={{opacity}}>{children}</div>;
}
 
function Page() {
  return (
    <ScrollAlphaHeader>
      <HeavyContent />
    </ScrollAlphaHeader>
  );
}
  • A) HeavyContent ๋‚ด๋ถ€์— memo ์„ ์–ธ์ด ๋น„๋ฐ€๋ฆฌ์— ๋ฆฌ์•กํŠธ์— ๊ธฐ๋ณธ ํƒ‘์žฌ๋˜์–ด ์žˆ์–ด์„œ.
  • B) ์•„๋น (Page) ์ปดํฌ๋„ŒํŠธ๋Š” ์ „ํ˜€ ์ƒํƒœ ๋ณ€ํ™”๊ฐ€ ์—†์œผ๋‹ˆ ์žฌ๋ Œ๋”๋ง์ด ์•ˆ ๋˜๊ณ , ์ด๋ฏธ ๋ Œ๋” ๋ฌถ์Œ์œผ๋กœ ์‹ธ์ค€ <HeavyContent/>์˜ ์ฐธ์กฐ ์ฃผ์†Œ๊ฐ’ ๊ป๋ฐ๊ธฐ ์ž์ฒด๊ฐ€ ScrollAlphaHeader์˜ ๋ฆฌ๋ Œ๋”๋ง ํŒŒ์ดํ”„๋ผ์ธ ์ค‘์—๋„ ์—ฌ์ „ํžˆ ๋˜‘๊ฐ™์ด ์œ ์ง€๋˜๊ธฐ ๋•Œ๋ฌธ.
  • C) CSS์˜ style={{opacity}} ๊ตฌ๋ฌธ์€ ๋ฆฌ์•กํŠธ ๊ฐ€์ƒ ๋” ์—”์ง„์„ ๋ฌด์‹œํ•˜๊ณ  ๋ธŒ๋ผ์šฐ์ €์—๊ฒŒ ์ง์ ‘ GPU ์—‘์…€์„ ๊ฑธ๊ธฐ ๋•Œ๋ฌธ.

โœ… ์ •๋‹ต: B

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค: ์ด๊ฒŒ Children ํŒจํ„ด ๋งˆ๋ฒ•์˜ ๊ทผ์›์ž…๋‹ˆ๋‹ค! ScrollAlphaHeader ๊ฐ€ ๋ฐฑ๋‚  ๋‚ด๋ถ€ ์ƒํƒœ(opacity)๋ฅผ ๋ฐ”๊พธ๋ฉฐ ํญ์ฃผ๊ธฐ๊ด€์ฐจ์ฒ˜๋Ÿผ ๋ฆฌ๋ Œ๋”๋ง์„ ๋Œ์•„๋ดค์ž, ์ž๊ธฐํ•œํ…Œ ์™ ๊ฝ‚ํ˜€์ง„ ํ–„๋ฒ„๊ฑฐ ํŒจํ‹ฐ(children === <HeavyContent/>)๋ฅผ ๋˜๋‹ค์‹œ ์ƒˆ๋กœ ๋งŒ๋“ค ๊ถŒํ•œ์€ ์—†์Šต๋‹ˆ๋‹ค. ํŒจํ‹ฐ(๊ฐ์ฒด ์ฐธ์กฐ๊ฐ’)๋ฅผ ๋งŒ๋“ค์–ด ๋„˜๊ฒจ์ฃผ๋Š” ๋‹น์‚ฌ์ž๋Š” ๋ฐ”๊นฅ์ชฝ์ธ Page ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ ์ด๋‹ˆ๊นŒ์š”. Page ์ž…์žฅ์—์„  ์•„๋ฌด ์ƒํƒœ ๋ณ€์ด๊ฐ€ ์—†์–ด ๊ฐ€๋งŒ~ํžˆ ๋ฉˆ์ถฐ์žˆ์œผ๋‹ˆ ์ € ๋ฌด๊ฑฐ์šด ์ปดํฌ๋„ŒํŠธ ์ฃผ์†Œ ๊ฐ์ฒด๋Š” ์˜ˆ์ „ ํฌ์žฅ์ง€ ๊ทธ๋Œ€๋กœ ์œ ์ž…๋˜๊ณ , ๋ฆฌ์•กํŠธ ์—”์ง„์€ "์˜คํ˜ธ ํŒจํ‹ฐ ์š”์†Œ ๊ฐ์ฒด๊ฐ€ ์ด์ „์ด๋ž‘ ๋˜‘๊ฐ™์€ ๋†ˆ ๊ป๋ฐ๊ธฐ๋„ค? ๊ทธ๋Ÿผ ์žฌํ™œ์šฉ!" ํ•˜๊ณ  ํŒจ์Šคํ•ด๋ฒ„๋ฆฝ๋‹ˆ๋‹ค.

Q3. ์˜์ฒ ์ด๊ฐ€ React.memo ๋ฐฉ์–ด๊ตฌ๋ฅผ ๋–ก์น ํ•˜๋Š” ๊ฒƒ์„ ์™œ '๋‚˜์œ ์„ค๊ณ„์— ๋Œ€ํ•œ ์ž„์‹œ ๋ฐ˜์ฐฝ๊ณ '๋ผ๊ณ  ๋น„ํŒ๋ฐ›๋Š”์ง€, ๊ทธ ์•ˆ์— ์ˆจ๊ฒจ์ง„ ์œ ์ง€๋ณด์ˆ˜์ƒ์˜ ํญํƒ„ ๋”œ๋ ˆ๋งˆ(์˜์กด์„ฑ ์ „ํŒŒ ์ง€์˜ฅ)๋ฅผ ์„œ์ˆ ํ•ด ๋ณด์„ธ์š”.

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

React.memo ๋Š” '๋ชจ๋“  props๊ฐ€ ํ•˜๋‚˜๋„ ์•ˆ ๋ฐ”๋€Œ์—ˆ์„ ๋•Œ'๋งŒ ์ž‘๋™ํ•˜๋Š” ๋งค์šฐ ์–์‚ฝํ•œ ๋ฐฉ์–ด๋ง‰์ด๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.
10๊ฐœ์˜ ํ•˜์œ„ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ „๋ถ€ Memo๋กœ ๋ง‰์•˜๋‹ค๊ณ  ์ž๋ž‘ํ•˜์ง€๋งŒ, ๋ฏธ๋ž˜์— ๋ˆ„๊ตฐ๊ฐ€ ๋ถ€๋ชจ์—์„œ ์•„์ฃผ ์‚ฌ์†Œํ•œ { userInfo: {...} } ๊ฐ์ฒด ํ•˜๋‚˜๋‚˜ onClose={() => {}} ํ•จ์ˆ˜ ํ•˜๋‚˜๋ผ๋„ ๋ณ„์ƒ๊ฐ ์—†์ด ํ”„๋กญ์Šค๋กœ ์ƒˆ๋กœ ๋˜์ง€๋Š” ์ˆœ๊ฐ„, Memo์˜ ์–•์€ ๋น„๊ต๊ฐ€ ๋ฐ•์‚ด ๋‚˜๋ฉฐ ํˆฌ๊ณผ ๋ฐ๋ฏธ์ง€๊ฐ€ ๋“ค์–ด๊ฐ€ ํ•˜์œ„ ์ปดํฌ๋„ŒํŠธ 10๊ฐœ๊ฐ€ ๋„๋ฏธ๋…ธ์ฒ˜๋Ÿผ ์™€๋ฅด๋ฅด ๋ฌด๋„ˆ์ ธ ๋‚ด๋ฆฝ๋‹ˆ๋‹ค.
์ด ํˆฌ๊ณผ ๋ฐ๋ฏธ์ง€๋ฅผ ๋ง‰์œผ๋ ค๋ฉด ๋˜ ๋ถ€๋ชจ๋กœ ์˜ฌ๋ผ๊ฐ€์„œ ๊ฐ์ฒด๋Š” useMemo๋กœ ๋ฌถ๊ณ  ํ•จ์ˆ˜๋Š” useCallback์œผ๋กœ ๋ฌถ๋Š” ๋ˆˆ๋ฌผ๊ฒจ์šด '์ตœ์ ํ™” Hooks ์ง€์˜ฅ'์ด ์ „์—ผ๋ณ‘์ฒ˜๋Ÿผ ๋ฒˆ์ง€๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์• ์ดˆ์— ๊ตฌ์กฐ(children ๊ฒฉ๋ฆฌ, State push down)๋กœ ๋ง‰์•˜๋”๋ผ๋ฉด ์ด๋”ฐ์œ„ ์˜ค๋ฒ„ํ—ค๋“œ์™€ ๊ฑฐ์ง€ ๊ฐ™์€ ์ถ”๊ฐ€ ์ฝ”๋“œ๋“ค์€ ๋‹จ ํ•œ ์ค„๋„ ํ•„์š”๊ฐ€ ์—†๊ฒŒ ๋˜์ฃ .