05. ๐Ÿช ๋ฆฌ์•กํŠธ ํ›…๊ณผ ๊ด€์‹ฌ์‚ฌ ๋ถ„๋ฆฌ ์„ค๊ณ„

2026๋…„ 3์›” 5์ผ ์ˆ˜์ •๋จ

๐Ÿ“‹ ๊ฐœ์š”

์ปค์Šคํ…€ ํ›…์„ ํ†ตํ•œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ๋ถ„๋ฆฌ์™€, ์™ธ๋ถ€ ์ƒํƒœ ๊ตฌ๋… ์‹œ ๋ฐœ์ƒํ•˜๋Š” ํ…Œ์–ด๋ง ํ˜„์ƒ์„ ๋ฐฉ์ง€ํ•˜๋Š” ๊ณ ๊ธ‰ ํ›… ์„ค๊ณ„ ๊ธฐ์ˆ ์„ ์ •๋ณตํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ“Œ ์ด ๋ฉด์ ‘ ํ•ญ๋ชฉ์˜ ๋ชฉํ‘œ

โฑ๏ธ ์˜ˆ์ƒ ์ฝ๊ธฐ ์‹œ๊ฐ„: 22๋ถ„ (ํ•ต์‹ฌ ์š”์•ฝ: 11๋ถ„)

๐Ÿ—บ๏ธ ์ด ์ฑ•ํ„ฐ์˜ ํ๋ฆ„
[๊ฐœ๋… ์‚ฌ์ „] โ†’ [์งˆ๋ฌธ 1: ๋กœ์ง ๋ถ„๋ฆฌ ๊ธฐ์ค€] โ†’ [์งˆ๋ฌธ 2: useSyncExternalStore] โ†’ [์‹ค์ „ ๋ณ€ํ˜• ์งˆ๋ฌธ]

๐ŸŽฏ ์ด ์ฑ•ํ„ฐ๋ฅผ ๋‹ค ์ฝ์œผ๋ฉด ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ

  • ๋ทฐ(View)์™€ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ๋ถ„๋ฆฌํ•˜๋Š” ๋ช…ํ™•ํ•œ ๊ธฐ์ค€์„ ์„ธ์šธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • useSyncExternalStore๋ฅผ ์‚ฌ์šฉํ•ด ์™ธ๋ถ€ ์ƒํƒœ๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ๊ตฌ๋…ํ•˜๋Š” ๋ฒ•์„ ์ดํ•ดํ•ฉ๋‹ˆ๋‹ค.
  • HOC์™€ ์ปค์Šคํ…€ ํ›… ํŒจํ„ด์˜ ์ ์žฌ์ ์†Œ ํ™œ์šฉ๋ฒ•์„ ๋…ผ๋ฆฌ์ ์œผ๋กœ ๋น„๊ตํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ“š ํ•ต์‹ฌ ๊ฐœ๋… ์‚ฌ์ „ (Concept Glossary)

1. ๊ด€์‹ฌ์‚ฌ ๋ถ„๋ฆฌ (Separation of Concerns, SoC)

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

2. ํ…Œ์–ด๋ง (Tearing)

๋™์‹œ์„ฑ ๋ Œ๋”๋ง ํ™˜๊ฒฝ์—์„œ ํ™”๋ฉด์˜ ์—ฌ๋Ÿฌ ๋ถ€๋ถ„์ด ์„œ๋กœ ๋‹ค๋ฅธ ์‹œ์ ์˜ ์ƒํƒœ๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ์‹œ๊ฐ์  ๋ถˆ์ผ์น˜ ํ˜„์ƒ์ž…๋‹ˆ๋‹ค. ๋ Œ๋”๋ง ๋„์ค‘ ์™ธ๋ถ€ ์ƒํƒœ(Store ๋“ฑ)๊ฐ€ ๋ฐ”๋€Œ์–ด ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

3. ๊ณ ์ฐจ ์ปดํฌ๋„ŒํŠธ (Higher-Order Component, HOC)

์ปดํฌ๋„ŒํŠธ๋ฅผ ์ธ์ž๋กœ ๋ฐ›์•„ ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•œ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ํŒจํ„ด์ž…๋‹ˆ๋‹ค. ํ›…์ด ๋“ฑ์žฅํ•˜๊ธฐ ์ „ ๋กœ์ง ์žฌ์‚ฌ์šฉ์˜ ํ•ต์‹ฌ์ด์—ˆ์œผ๋ฉฐ, ํ˜„์žฌ๋Š” ๋กœ๊น…์ด๋‚˜ ๊ถŒํ•œ ์ฒดํฌ ๋“ฑ UI๋ฅผ ๊ฐ์‹ธ๋Š” ๋กœ์ง์— ์ฃผ๋กœ ์“ฐ์ž…๋‹ˆ๋‹ค.


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

  • ๐Ÿฃ ์˜์ฒ  ( ์‹ ์ž… ): "์˜ํ˜ธ ๋‹˜! '์˜์ˆ˜๋„ค ๋Œ€์‹œ๋ณด๋“œ' ์ปดํฌ๋„ŒํŠธ๊ฐ€ 500์ค„์ด ๋„˜์–ด๊ฐ€์„œ ๋„์ €ํžˆ ๋ชป ๊ณ ์น˜๊ฒ ์–ด์š”. ์•ˆ์— API ํ˜ธ์ถœ, ์ •๋ ฌ ๋กœ์ง, ํ•„ํ„ฐ ๋กœ์ง์ด ๋‹ค ์„ž์—ฌ ์žˆ์–ด์„œ ํ•˜๋‚˜ ๋ฐ”๊พธ๋ฉด ๋‹ค ํ„ฐ์ ธ์š”! ๐Ÿ˜ญ"
  • ๐Ÿฆ ์˜ํ˜ธ ( ๋ฆฌ๋“œ ): "์˜์ฒ  ๋‹˜, ๊ทธ๊ฒŒ ๋ฐ”๋กœ '์ŠคํŒŒ๊ฒŒํ‹ฐ ์ปดํฌ๋„ŒํŠธ'์˜ ์ „ํ˜•์ž…๋‹ˆ๋‹ค. ์ปดํฌ๋„ŒํŠธ๋Š” UI๋งŒ ๊ทธ๋ ค์•ผ ํ•ด์š”. ๊ณ„์‚ฐ๊ธฐ ์—”์ง„์ด๋ž‘ ๊ป๋ฐ๊ธฐ๋ฅผ ๋ถ„๋ฆฌํ•˜์ง€ ์•Š์œผ๋ฉด ํ‰์ƒ ์œ ์ง€๋ณด์ˆ˜์˜ ๋Šช์—์„œ ๋ชป ๋‚˜์˜ฌ ๊ฒ๋‹ˆ๋‹ค. ์ž, ์ปค์Šคํ…€ ํ›…์œผ๋กœ '๋‡Œ'๋ฅผ ์ถ”์ถœํ•ด ๋ด…์‹œ๋‹ค."

Q1. ๊ด€์‹ฌ์‚ฌ์˜ ๋ถ„๋ฆฌ๋ฅผ ์œ„ํ•ด ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ์ปค์Šคํ…€ ํ›…์œผ๋กœ ์ถ”์ถœํ•  ๋•Œ, ๋ทฐ(View)์™€ ๋กœ์ง์„ ๋ถ„๋ฆฌํ•˜๋Š” ๊ธฐ์ค€์€ ๋ฌด์—‡์ธ๊ฐ€์š”?

๐ŸŽฏ ์ถœ์ œ ์˜๋„

์ฝ”๋“œ๋ฅผ ๋‹จ์ˆœํžˆ ์˜ฎ๊ธฐ๋Š” ํ–‰์œ„๋ฅผ ๋„˜์–ด, ์ปดํฌ๋„ŒํŠธ์˜ ์ฑ…์ž„์„ ์–ด๋–ป๊ฒŒ ์ •์˜ํ•˜๋Š”์ง€ ๊ทธ๋ฆฌ๊ณ  ์žฌ์‚ฌ์šฉ์„ฑ๊ณผ ํ…Œ์ŠคํŠธ ์šฉ์ด์„ฑ์„ ๊ณ ๋ คํ•œ ์„ค๊ณ„ ์ฒ ํ•™์ด ์žˆ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

๐Ÿฃ ์˜์ฒ ์ด์˜ Naive ๊ตฌํ˜„ (Bad Case)

์˜์ฒ ์ด๋Š” ์ปดํฌ๋„ŒํŠธ ์•ˆ์— ๋ชจ๋“  ๊ฒƒ์„ ๋‹ค ๋•Œ๋ ค ๋ฐ•์•˜์Šต๋‹ˆ๋‹ค.

// ๐Ÿฃ ์˜์ฒ : "์ฐพ์•„๋ณด๊ธฐ ํŽธํ•˜๊ฒŒ ํ•œ ํŒŒ์ผ์— ๋‹ค ๋ชจ์•„๋†จ์–ด์š”!"
function UserDashboard() {
  const [data, setData] = useState([]);
  
  useEffect(() => {
    // โš ๏ธ API ํ˜ธ์ถœ ๋กœ์ง ์ง์ ‘ ๋…ธ์ถœ
    fetch('/api/users').then(res => res.json()).then(setData);
  }, []);
 
  // โš ๏ธ ๋ณต์žกํ•œ ํ•„ํ„ฐ/์ •๋ ฌ ๋กœ์ง ์ง์ ‘ ๋…ธ์ถœ
  const sortedData = [...data].sort((a, b) => b.score - a.score);
  const activeUsers = sortedData.filter(u => u.isActive);
 
  return (
    <div>
      {activeUsers.map(user => <UserItem key={user.id} {...user} />)}
    </div>
  );
}

๐Ÿฆ ์˜ํ˜ธ์˜ ํŒฉํญ ์กฐ์–ธ
"์˜์ฒ  ๋‹˜, ์ด๋ ‡๊ฒŒ ์งœ๋ฉด '๋ฐ์ดํ„ฐ ํ•„ํ„ฐ๋ง ๋กœ์ง'๋งŒ ๋”ฐ๋กœ ํ…Œ์ŠคํŠธํ•˜๊ณ  ์‹ถ์„ ๋•Œ ์ปดํฌ๋„ŒํŠธ ์ „์ฒด๋ฅผ ๋ Œ๋”๋งํ•ด์•ผ ํ•ด์š”. ์ปดํฌ๋„ŒํŠธ๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์„œ ๊ทธ๋ฆผ์„ ๊ทธ๋ฆฌ๋Š” ๊ฒƒ์—๋งŒ ์ง‘์ค‘ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋‚˜๋จธ์ง€๋Š” ๋‹ค ํ›…์œผ๋กœ ์œ ๋ฐฐ ๋ณด๋‚ด์„ธ์š”."

๐Ÿฆ ์˜ํ˜ธ์˜ ์•„ํ‚คํ…์ฒ˜ ๊ฐ€์ด๋“œ (Good Case)

์˜ํ˜ธ ๋ฆฌ๋“œ๋Š” ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ useUserBoard๋ผ๋Š” ์ปค์Šคํ…€ ํ›…์œผ๋กœ ๋ถ„๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

// ๐Ÿฆ ์˜ํ˜ธ: "์ปดํฌ๋„ŒํŠธ๋Š” UI ๋ช…์„ธ์„œ์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ƒ์„ธ ๋กœ์ง์€ ํ›…์ด๋ผ๋Š” ํ•˜์ฒญ์—…์ฒด์— ๋งก๊ธฐ์„ธ์š”."
 
// ๐Ÿง  Business Logic Hook
function useUserBoard() {
  const [users, setUsers] = useState([]);
  // ... fetch users๋กœ์ง
  
  const processUsers = (data) => {
    return data
      .sort((a, b) => b.score - a.score)
      .filter(u => u.isActive);
  };
 
  return { users: processUsers(users) };
}
 
// ๐Ÿ–ผ๏ธ View Component
function UserDashboard() {
  const { users } = useUserBoard(); // ๐Ÿช„ ๋งˆ๋ฒ•์ฒ˜๋Ÿผ ๋ฐ์ดํ„ฐ๋งŒ ์™!
 
  return (
    <div>
      {users.map(user => <UserItem key={user.id} {...user} />)}
    </div>
  );
}

๐Ÿ“Š ๋ ˆ๋ฒจ๋ณ„ ๋‹ต๋ณ€ ๊ฐ€์ด๋“œ (Self-Check)

  • Level 1 (Junior): "์žฌ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ์€ ๋กœ์ง์ด ์ƒ๊ธฐ๋ฉด ์ปค์Šคํ…€ ํ›…์œผ๋กœ ๋งŒ๋“ญ๋‹ˆ๋‹ค. ์ปดํฌ๋„ŒํŠธ ์ฝ”๋“œ๊ฐ€ ๋„ˆ๋ฌด ๊ธธ์–ด์งˆ ๋•Œ๋„ ๋ถ„๋ฆฌํ•ฉ๋‹ˆ๋‹ค."
  • Level 2 (Senior): "๋ฐ์ดํ„ฐ ํŽ˜์นญ, ์ƒํƒœ ๊ด€๋ฆฌ, ๋ณต์žกํ•œ ์—ฐ์‚ฐ ๋“ฑ '๋„๋ฉ”์ธ ๋กœ์ง'๊ณผ 'UI ๋ Œ๋”๋ง'์„ ๋ถ„๋ฆฌํ•˜๋Š” ๊ธฐ์ค€์„ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด UI ๋ณ€๊ฒฝ ์—†์ด ๋กœ์ง๋งŒ ๋…๋ฆฝ์ ์œผ๋กœ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ(Unit Test)๋ฅผ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•จ์„ ๊ฐ•์กฐํ•ฉ๋‹ˆ๋‹ค."
  • Level 3 (Specialist): "'Headless Component' ํŒจํ„ด์ด๋‚˜ 'Hook ๊ธฐ๋ฐ˜ ์•„ํ‚คํ…์ฒ˜'๋ฅผ ์–ธ๊ธ‰ํ•ฉ๋‹ˆ๋‹ค. UI ํ”„๋ฆฌ์  ํ…Œ์ด์…˜๊ณผ ์ƒํƒœ ๊ด€๋ฆฌ ์—”์ง„์„ ์™„์ „ํžˆ ๊ฒฉ๋ฆฌํ•˜์—ฌ, ์›น๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์•ฑ(React Native) ๋“ฑ ๋‹ค๋ฅธ ํ”Œ๋žซํผ์—์„œ๋„ ๋™์ผํ•œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ํ›…์„ ๊ณต์œ ํ•  ์ˆ˜ ์žˆ๋Š” ํ™•์žฅ์„ฑ ์žˆ๋Š” ๊ตฌ์กฐ๋ฅผ ์ œ์•ˆํ•ฉ๋‹ˆ๋‹ค."

Q2. useSyncExternalStore ํ›…์ด ๋„์ž…๋œ ๋ฐฐ๊ฒฝ๊ณผ, ํ…Œ์–ด๋ง(Tearing) ํ˜„์ƒ์„ ๋ฐฉ์ง€ํ•˜๋Š” ์›๋ฆฌ๋Š” ๋ฌด์—‡์ธ๊ฐ€์š”?

๐ŸŽฏ ์ถœ์ œ ์˜๋„

React 18 ๋™์‹œ์„ฑ ๋ชจ๋“œ์—์„œ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ๋ฏธ๋ฌ˜ํ•œ ์‹œ๊ฐ์  ๋ฒ„๊ทธ๋ฅผ ์ดํ•ดํ•˜๊ณ , ์™ธ๋ถ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ(Redux, Zustand ๋“ฑ)๊ฐ€ ๋ฆฌ์•กํŠธ ์Šค์ผ€์ค„๋Ÿฌ์™€ ์–ด๋–ป๊ฒŒ ํ˜‘์—…ํ•˜๋Š”์ง€ ๊นŠ์ด ์žˆ๊ฒŒ ์•„๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

๐Ÿฃ ์˜์ฒ ์ด์˜ Naive ๊ตฌํ˜„ (Bad Case)

์˜์ฒ ์ด๋Š” window.innerWidth๋ฅผ ์ „์—ญ ์ƒํƒœ์ฒ˜๋Ÿผ ์“ฐ๊ธฐ ์œ„ํ•ด ๋‹จ์ˆœํ•œ useEffect๋ฅผ ์ผ๋‹ค๊ฐ€ ๋ ˆ์ด์•„์›ƒ์ด ์ฐข์–ด์ง€๋Š” ํ˜„์ƒ์„ ๋ฐœ๊ฒฌํ–ˆ์Šต๋‹ˆ๋‹ค.

// ๐Ÿฃ ์˜์ฒ : "๊ทธ๋ƒฅ useEffect๋กœ ๊ฐ’ ๊ฐ์‹œํ•˜๋ฉด ๋˜๋Š” ๊ฑฐ ์•„๋‹Œ๊ฐ€์š”?"
function useWindowWidth() {
  const [width, setWidth] = useState(window.innerWidth);
  
  useEffect(() => {
    const handleResize = () => setWidth(window.innerWidth);
    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);
 
  return width; // โš ๏ธ ๋™์‹œ์„ฑ ๋ชจ๋“œ์—์„œ ์—ฌ๋Ÿฌ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์„œ๋กœ ๋‹ค๋ฅธ width๋ฅผ ์ฝ์„ ์œ„ํ—˜(Tearing) ์žˆ์Œ
}

๐Ÿฆ ์˜ํ˜ธ์˜ ํŒฉํญ ์กฐ์–ธ
"์˜์ฒ  ๋‹˜, ๋ฆฌ์•กํŠธ 18์˜ ๋™์‹œ์„ฑ ๋ Œ๋”๋ง์€ ๋„์ค‘์— ๋ฉˆ์ถœ ์ˆ˜ ์žˆ์–ด์š”. ๋ Œ๋”๋ง 1๋‹จ๊ณ„์—์„œ A ์ปดํฌ๋„ŒํŠธ๊ฐ€ width๋ฅผ ์ฝ๊ณ , ๊ทธ ์‚ฌ์ด์— ๋ฆฌ์‚ฌ์ด์ฆˆ๊ฐ€ ๋ฐœ์ƒํ•œ ๋’ค 2๋‹จ๊ณ„์—์„œ B ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ฝ์œผ๋ฉด ํ™”๋ฉด์ด ์ง์ง์ด๊ฐ€ ๋ฉ๋‹ˆ๋‹ค. ์ด๊ฑธ ๋ง‰์œผ๋ผ๊ณ  ๋งŒ๋“  ๊ฒŒ useSyncExternalStore์ž…๋‹ˆ๋‹ค."

๐Ÿฆ ์˜ํ˜ธ์˜ ์•„ํ‚คํ…์ฒ˜ ๊ฐ€์ด๋“œ (Good Case)

์˜ํ˜ธ ๋ฆฌ๋“œ๊ฐ€ ์•ˆ์ „ํ•œ ์™ธ๋ถ€ ์ƒํƒœ ๊ตฌ๋… ๋ฐฉ์‹์„ ์ œ์•ˆํ•ฉ๋‹ˆ๋‹ค.

// ๐Ÿฆ ์˜ํ˜ธ: "์™ธ๋ถ€ ์ƒํƒœ๋Š” ๋ฆฌ์•กํŠธ์˜ ๋ Œ๋”๋ง ์†๋„์™€ ๋งž์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ•์ œ๋กœ ๋™๊ธฐํ™”ํ•˜์„ธ์š”."
 
const widthStore = {
  subscribe: (cb) => {
    window.addEventListener('resize', cb);
    return () => window.removeEventListener('resize', cb);
  },
  getSnapshot: () => window.innerWidth,
};
 
function useWindowWidth() {
  // โœ… ๋ฆฌ์•กํŠธ์—๊ฒŒ '์ด ๊ฐ’์€ ์™ธ๋ถ€์—์„œ ์˜ค๋‹ˆ๊นŒ ๋ Œ๋”๋ง ์ค‘์— ๋ฐ”๋€Œ๋ฉด ์ค‘๋‹จํ•ด!'๋ผ๊ณ  ์•Œ๋ ค์คŒ
  return useSyncExternalStore(
    widthStore.subscribe,
    widthStore.getSnapshot
  );
}

๐Ÿ“Š ๋ ˆ๋ฒจ๋ณ„ ๋‹ต๋ณ€ ๊ฐ€์ด๋“œ (Self-Check)

  • Level 1 (Junior): "์™ธ๋ถ€ ์ƒํƒœ(Redux ๋“ฑ)๋ฅผ ๋ฆฌ์•กํŠธ์™€ ์—ฐ๊ฒฐํ•ด ์ฃผ๋Š” ํ›…์ž…๋‹ˆ๋‹ค. ๊ฐ’์ด ๋ฐ”๋€Œ๋ฉด ๋ฆฌ๋ Œ๋”๋งํ•ด ์ค๋‹ˆ๋‹ค."
  • Level 2 (Senior): "๋™์‹œ์„ฑ ๋ Œ๋”๋ง์€ ์ž‘์—…์„ ์ชผ๊ฐœ์–ด ์ฒ˜๋ฆฌํ•˜๋Š”๋ฐ, ๊ทธ ์‚ฌ์ด ์™ธ๋ถ€ ์ƒํƒœ๊ฐ€ ๋ณ€ํ•˜๋ฉด ์‹œ๊ฐ์  ๋ถˆ์ผ์น˜(Tearing)๊ฐ€ ๋ฐœ์ƒํ•จ์„ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค. useSyncExternalStore๋Š” ์ฝ๊ธฐ ์ž‘์—…์˜ ์ผ๊ด€์„ฑ์„ ๋ณด์žฅํ•˜๊ธฐ ์œ„ํ•ด ๋„์ž…๋˜์—ˆ์Œ์„ ์–ธ๊ธ‰ํ•ฉ๋‹ˆ๋‹ค."
  • Level 3 (Specialist): "์ด ํ›…์ด ์›์ž์ (Atomic) ์—…๋ฐ์ดํŠธ๋ฅผ ์–ด๋–ป๊ฒŒ ๋ณด์žฅํ•˜๋Š”์ง€ ๋‚ด๋ถ€ ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค. ๋ Œ๋”๋ง ์ค‘ ์™ธ๋ถ€ ๊ฐ’์ด ๋ณ€ํ•˜๋ฉด ๋‹ค์‹œ ๋ Œ๋”๋ง์„ ์‹œ๋„ํ•˜๊ฑฐ๋‚˜ ๋™๊ธฐ์‹ ์—…๋ฐ์ดํŠธ๋กœ ์ „ํ™˜ํ•˜์—ฌ ์‚ฌ์šฉ์ž์—๊ฒŒ ํ•ญ์ƒ ์ผ๊ด€๋œ ์Šค๋ƒ…์ƒท์„ ๋ณด์—ฌ์ฃผ๋Š” '์ผ๊ด€์„ฑ ๋ณด์žฅ' ์ „๋žต์„ ๋ถ„์„ํ•ฉ๋‹ˆ๋‹ค."

Q182. ๊ณ ์ฐจ ์ปดํฌ๋„ŒํŠธ(HOC) ํŒจํ„ด๊ณผ ์ปค์Šคํ…€ ํ›… ํŒจํ„ด์˜ ์žฅ๋‹จ์ ์„ ๋น„๊ตํ•ด ๋ณด์„ธ์š”.

  • ๐ŸŽฏ ์ถœ์ œ ์˜๋„: ๋ฆฌ์•กํŠธ์˜ ๋กœ์ง ์žฌ์‚ฌ์šฉ ํŒจํ„ด ๋ณ€์ฒœ์‚ฌ๋ฅผ ์ดํ•ดํ•˜๊ณ , ์ƒํ™ฉ์— ๋งž๋Š” ๊ฐ€์žฅ ์ ์ ˆํ•œ ๋„๊ตฌ๋ฅผ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
  • ๐Ÿ’ก ํ•ต์‹ฌ ์›๋ฆฌ & ๋‹ต๋ณ€: ์ปค์Šคํ…€ ํ›…์€ ํ•จ์ˆ˜ํ˜• ์ปดํฌ๋„ŒํŠธ์˜ ํ๋ฆ„ ์•ˆ์—์„œ ์ƒํƒœ์™€ ๋กœ์ง์„ ๋งค์šฐ ์œ ์—ฐํ•˜๊ฒŒ ์กฐํ•ฉํ•  ์ˆ˜ ์žˆ์–ด ํ˜„์žฌ ๋ฆฌ์•กํŠธ์˜ ํ‘œ์ค€์ž…๋‹ˆ๋‹ค. ๋ฐ˜๋ฉด, HOC๋Š” ์›๋ณธ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ฐ์‹ธ๋Š” ๊ตฌ์กฐ ํŠน์„ฑ์ƒ Props๊ฐ€ ์–ด๋””์„œ ์˜ค๋Š”์ง€ ๋ถˆ๋ช…ํ™•ํ•ด์ง€๋Š” 'Props Drilling'์ด๋‚˜ '์ด๋ฆ„ ์ถฉ๋Œ' ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜๊ธฐ ์‰ฝ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ธ์ฆ ์ฒดํฌ๋‚˜ ๋กœ๊น…์ฒ˜๋Ÿผ UI ์ž์ฒด๋ฅผ ๊ฐ์‹ธ๊ฑฐ๋‚˜ ์ฃผ์ž…ํ•ด์•ผ ํ•˜๋Š” ์‹œ๊ฐ์ ์ธ ํšก๋‹จ ๊ด€์‹ฌ์‚ฌ(Cross-cutting Concerns)์—๋Š” ์—ฌ์ „ํžˆ HOC๊ฐ€ ์„ ์–ธ์ ์ด๊ณ  ๊น”๋”ํ•œ ๋Œ€์•ˆ์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Q249. ๋ฆฌ์•กํŠธ์—์„œ ๋””๋ฐ”์šด์Šค(Debounce)์™€ ์“ฐ๋กœํ‹€(Throttle)์„ ์ ์šฉํ•  ๋•Œ ์ฃผ์˜ํ•  ์ ์€ ๋ฌด์—‡์ธ๊ฐ€์š”?

  • ๐ŸŽฏ ์ถœ์ œ ์˜๋„: ๋ฆฌ๋ Œ๋”๋ง ์‹œ๋งˆ๋‹ค ํ•จ์ˆ˜๊ฐ€ ์ƒˆ๋กœ ์ƒ์„ฑ๋˜๋Š” ๋ฆฌ์•กํŠธ์˜ ํŠน์„ฑ๊ณผ ํƒ€์ด๋จธ ๋ฉ”์ปค๋‹ˆ์ฆ˜ ๊ฐ„์˜ ์ถฉ๋Œ์„ ๋ฐฉ์–ดํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
  • ๐Ÿ’ก ํ•ต์‹ฌ ์›๋ฆฌ & ๋‹ต๋ณ€: ๋ฆฌ์•กํŠธ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ฆฌ๋ Œ๋”๋ง๋  ๋•Œ๋งˆ๋‹ค ์ •์˜๋œ ํ•จ์ˆ˜๋Š” ์ƒˆ๋กœ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค. ๋‹จ์ˆœํ•˜๊ฒŒ lodash.debounce๋ฅผ ํ•จ์ˆ˜ ์•ˆ์— ์„ ์–ธํ•˜๋ฉด ๋ฆฌ๋ Œ๋”๋ง๋งˆ๋‹ค ์ƒˆ๋กœ์šด ํƒ€์ด๋จธ๊ฐ€ ์ƒ๊ฒจ ์ œ ๊ธฐ๋Šฅ์„ ๋ชป ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ•ด๊ฒฐํ•˜๋ ค๋ฉด useMemo๋‚˜ useCallback์œผ๋กœ ๋””๋ฐ”์šด์Šค๋œ ํ•จ์ˆ˜ ์ธ์Šคํ„ด์Šค๋ฅผ ์œ ์ง€ํ•˜๊ฑฐ๋‚˜, useRef๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ Œ๋”๋ง ์‚ฌ์ดํด๊ณผ ๋ฌด๊ด€ํ•˜๊ฒŒ ํƒ€์ด๋จธ ์ƒํƒœ๋ฅผ ๋ณด์กดํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ ์ปดํฌ๋„ŒํŠธ ์–ธ๋งˆ์šดํŠธ ์‹œ ๋ฐ˜๋“œ์‹œ cancel์„ ํ˜ธ์ถœํ•˜์—ฌ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜์™€ ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์ƒํƒœ ์—…๋ฐ์ดํŠธ๋ฅผ ๋ฐฉ์ง€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

Q258. ๋กœ์ปฌ ์Šคํ† ๋ฆฌ์ง€ ๋™๊ธฐํ™” ํ›…์„ ์„ค๊ณ„ํ•  ๋•Œ SSR ํ™˜๊ฒฝ์˜ 'window is not defined' ์—๋Ÿฌ๋ฅผ ์–ด๋–ป๊ฒŒ ํ•ด๊ฒฐํ•˜๋‚˜์š”?

  • ๐ŸŽฏ ์ถœ์ œ ์˜๋„: ์œ ๋‹ˆ๋ฒ„์„ค(Universal) ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ์‹คํ–‰ ํ™˜๊ฒฝ์— ๋Œ€ํ•œ ์ดํ•ด์™€ Next.js ๋“ฑ SSR ํ”„๋ ˆ์ž„์›Œํฌ์—์„œ์˜ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
  • ๐Ÿ’ก ํ•ต์‹ฌ ์›๋ฆฌ & ๋‹ต๋ณ€: ์„œ๋ฒ„ ์‚ฌ์ด๋“œ ๋ Œ๋”๋ง ์‹œ์—๋Š” ๋ธŒ๋ผ์šฐ์ € ๊ฐ์ฒด์ธ window๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ useState์˜ ์ดˆ๊ธฐ๊ฐ’์œผ๋กœ ์Šคํ† ๋ฆฌ์ง€๋ฅผ ์ง์ ‘ ์ฝ์œผ๋ ค ํ•˜๋ฉด ์—๋Ÿฌ๊ฐ€ ๋‚ฉ๋‹ˆ๋‹ค. ํ•ด๊ฒฐ์ฑ…์œผ๋กœ๋Š” useEffect ๋‚ด์—์„œ๋งŒ window์— ์ ‘๊ทผํ•˜์—ฌ ๋Ÿฐํƒ€์ž„์— ๊ฐ’์„ ์„ค์ •ํ•˜๊ฑฐ๋‚˜, typeof window !== 'undefined' ์กฐ๊ฑด๋ฌธ์œผ๋กœ ๊ฐ€๋“œ ๋กœ์ง์„ ์ž‘์„ฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ ์ตœ์‹  ๋ฆฌ์•กํŠธ์—์„œ๋Š” useSyncExternalStore์˜ ์„ธ ๋ฒˆ์งธ ์ธ์ž์ธ getServerSnapshot์„ ํ™œ์šฉํ•˜์—ฌ ์„œ๋ฒ„์šฉ ๊ธฐ๋ณธ๊ฐ’์„ ๋”ฐ๋กœ ์ œ๊ณตํ•จ์œผ๋กœ์จ ํ•˜์ด๋“œ๋ ˆ์ด์…˜ ๋ถˆ์ผ์น˜(Hydration Mismatch)๋ฅผ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿฃ ์˜์ฒ ์ด์˜ ๋ณต๊ธฐ ์ผ๊ธฐ

์˜ค๋Š˜ 'ํ…Œ์–ด๋ง'์ด๋ผ๋Š” ๋ง์„ ์ฒ˜์Œ ๋“ค์—ˆ๋Š”๋ฐ, ํ™”๋ฉด์ด ์ฐข์–ด์ง„๋‹ค๋Š” ํ‘œํ˜„์ด ๋„ˆ๋ฌด ๋ฌด์„ญ๊ฒŒ ๋‹ค๊ฐ€์™”๋‹ค. ๊ทธ๋™์•ˆ ๋‚ด๊ฐ€ ๋งŒ๋“  ์ „์—ญ ์ƒํƒœ ๊ตฌ๋… ๋กœ์ง๋“ค์ด ์ด ์œ„ํ—˜์— ๋…ธ์ถœ๋˜์–ด ์žˆ์—ˆ๋‹ค๋‹ˆ... ๋ฆฌ์•กํŠธ๊ฐ€ ์™œ useSyncExternalStore๋ผ๋Š” ๊ดด์ƒํ•œ ์ด๋ฆ„์˜ ํ›…์„ ๋งŒ๋“ค์—ˆ๋Š”์ง€ ์ด์ œ์•ผ ์ดํ•ด๊ฐ€ ๊ฐ„๋‹ค.

๐Ÿ’ก "์ปดํฌ๋„ŒํŠธ์—์„œ ์ค‘์š”ํ•œ ๊ฑด ๋ฌด์—‡์„ ๊ทธ๋ฆฌ๋А๋ƒ๊ฐ€ ์•„๋‹ˆ๋ผ, ๋ฌด์—‡์„ ๊ทธ๋ฆฌ์ง€ ์•Š๊ธฐ๋กœ ๊ฒฐ์ •ํ•˜๋А๋ƒ์— ์žˆ๋‹ค."

๋‚ด์ผ์€ ์ƒํƒœ ๊ด€๋ฆฌ์˜ ์ „์Ÿํ„ฐ, 'Flux ์•„ํ‚คํ…์ฒ˜์™€ ์ „์—ญ ์ƒํƒœ'๋ฅผ ๊ณต๋ถ€ํ•œ๋‹ค. Redux๋ถ€ํ„ฐ Zustand๊นŒ์ง€, ์ด ์ˆ˜๋งŽ์€ ๋„๊ตฌ๋“ค ์ค‘์—์„œ ์šฐ๋ฆฌ ์˜์ˆ˜๋„ค ์ปค๋ฎค๋‹ˆํ‹ฐ์— ๋”ฑ ๋งž๋Š” ์นผ์„ ๊ณจ๋ผ๋‚ด์•ผ์ง€! โš”๏ธ๐Ÿ›ก๏ธ