๐Ÿ’ก 13. ์ปดํŒŒ์šด๋“œ ์ปดํฌ๋„ŒํŠธ (Compound Components)

๐Ÿ“‹ ๊ฐœ์š”

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

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

  • <Select> ํƒœ๊ทธ์™€ <option> ํƒœ๊ทธ๋“ค์ฒ˜๋Ÿผ, ์—ฌ๋Ÿฌ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํ•˜๋‚˜๋กœ ๋ญ‰์ณ ์ž‘๋™ํ•˜๋Š” ํŒจํ„ด์˜ ์›๋ฆฌ๋ฅผ ํŒŒ์•…ํ•œ๋‹ค.
  • Props ์ง€์˜ฅ ์—†์ด๋„, ๋ถ€๋ชจ ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ์ž์‹ ์กฐ๊ฐ(๋ธ”๋ก)๋“ค์—๊ฒŒ ์กฐ์šฉํžˆ ์ƒํƒœ๋ฅผ ํผํŠธ๋ ค์ฃผ๋Š” ๋งˆ๋ฒ•(Context ์—ฐ๊ณ„)์„ ์Šค์Šค๋กœ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ์‚ฌ๋‚ด ๋””์ž์ธ ์‹œ์Šคํ…œ(UI ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ)์„ ๋งŒ๋“ค 0ํ‹ฐ์–ด ํ•ต์‹ฌ ์ง€์‹(Dot Notation ํ‘œ๊ธฐ๋ฒ•)์„ ์†์— ๋„ฃ๋Š”๋‹ค.

๐Ÿ“‹ ๋ชฉ์ฐจ


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

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

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

  • ์˜์ˆ˜(PM):"์˜ํ˜ธ ๋‹˜! ์šฐ๋ฆฌ๊ฐ€ ์ง์ ‘ ๋งŒ๋“  ๋“œ๋กญ๋‹ค์šด(Dropdown) ๊ณตํ†ต ์ปดํฌ๋„ŒํŠธ ๋ง์ด์—์š”. ์–ด๋–ค ํŽ˜์ด์ง€์—์„  ๋ˆŒ๋ €์„ ๋•Œ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ๋„ฃ๊ณ  ์‹ถ๊ณ , ์–ด๋–ค ๊ณณ์—์„  ํ•ญ๋ชฉ๋“ค์— ์•„์ด์ฝ˜์„ ๋ฐ•๊ณ  ์‹ถ๋Œ€์š”."
  • ์˜ํ˜ธ(๋ฆฌ๋“œ):"์˜์ฒ  ๋‹˜, ํ˜น์‹œ ๋˜ ๋“œ๋กญ๋‹ค์šด์— hasAnimation, iconType ํ”„๋กญ์Šค ๋šซ๊ณ  ๊ณ„์…จ์Šต๋‹ˆ๊นŒ?"
  • ์˜์ฒ (์‹ ์ž…):(ํ ์นซ) "...์–ด๋–ป๊ฒŒ ์•„์…จ์–ด์š”?"
  • ์˜ํ˜ธ(๋ฆฌ๋“œ): "๋‹ค ์ง€์šฐ์‹œ๊ณ , ์ €๋ฅผ ๋”ฐ๋ผ์˜ค์„ธ์š”. ์šฐ๋ฆฌ๊ฐ€ ๋งŒ๋“ค ๊ฑด ์™„์ œํ’ˆ ํ–„๋ฒ„๊ฑฐ๊ฐ€ ์•„๋‹ˆ๋ผ, ๊ทธ์ € ๋นต, ํŒจํ‹ฐ, ์น˜์ฆˆ๋ผ๋Š” ์žฌ๋ฃŒ ํ†ต์ผ ๋ฟ์ž…๋‹ˆ๋‹ค. ์ปดํŒŒ์šด๋“œ ํŒจํ„ด์„ ์•Œ๋ ค๋“œ๋ฆฌ์ฃ ."

๐Ÿค” ์™œ ์•Œ์•„์•ผ ํ•˜๋Š”๊ฐ€: Props๋กœ ์šฑ์—ฌ๋„ฃ๊ธฐ์˜ ํ•œ๊ณ„์น˜ ๋„๋‹ฌ

์šฐ๋ฆฌ๋Š” ์ž์ฃผ ๋ณตํ•ฉ์ ์ธ UI ์š”์†Œ๋“ค์„ ๋งŒ๋‚ฉ๋‹ˆ๋‹ค.
์•„์ฝ”๋””์–ธ(์—ด๋ ธ๋‹ค ๋‹ซํžˆ๋Š” ํŒจ๋„), ํƒญ(Tab), ๋กœ๋”ฉ์ด ๋„๋Š” ๋“œ๋กญ๋‹ค์šด ๋ฉ”๋‰ด, ์Šฌ๋ผ์ด๋” ์บ๋Ÿฌ์…€ ๋“ฑ์ด ๋Œ€ํ‘œ์ ์ด์ฃ .

๐Ÿค” ์ž ๊น, ๋จผ์ € ์ƒ๊ฐํ•ด๋ด
๋“œ๋กญ๋‹ค์šด ์ปดํฌ๋„ŒํŠธ ํ•˜๋‚˜์— ๋ฐฐ์—ด ๋ฐ์ดํ„ฐ(options)๋งŒ ์ƒ์งœ ํ”„๋กญ์Šค๋กœ ๋˜์ ธ์„œ ๊ทธ๋ฆด ๋•Œ์˜ ๋”์ฐํ•จ์„ ๋А๊ปด๋ณธ ์  ์—†๋Š”๊ฐ€?

// โŒ ์˜์ฒ ์ด์˜ ๋”์ฐํ•œ JSON ๋…ธ๊ฐ€๋‹ค ์„ ์–ธํ˜• ๋“œ๋กญ๋‹ค์šด
const myOptions = [
  { label: "๋‚ด ์ •๋ณด", value: "info", icon: "๐Ÿ‘ค", disabled: false },
  { label: "์‚ญ์ œํ•˜๊ธฐ", value: "delete", icon: "๐Ÿ—‘๏ธ", disabled: true, textDanger: true } // ๐Ÿšจ ์ปค์Šคํ…€์˜ ํ•œ๊ณ„
];
 
// ์ด ๊ป๋ฐ๊ธฐ ํ•˜๋‚˜๋กœ ๋‚ด๋ถ€์˜ ๋ฏธ์„ธํ•œ ์ƒ‰์ƒ, ์•„์ด์ฝ˜ ์กฐ์ •์ด ๊ฐ€๋Šฅํ• ๊นŒ?
<SuperDropdown 
  options={myOptions} 
  onChange={handleChange} 
  closeOnSelect={true}
  animation={{ speed: 300, type: 'fade' }} // ์ ์  ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ๊ฐ€ ๊ธฐ๊ดดํ•ด์ง„๋‹ค!
/>

์œ„ ์ฝ”๋“œ๋Š” 12๊ฐ•์—์„œ ๋งํ–ˆ๋˜ ๊ฐœ๋ฐฉ-ํ์‡„ ์›์น™ ํŒŒ๊ดด์˜ ์ „ํ˜•์ž…๋‹ˆ๋‹ค. ์ด ๋ฐ์ดํ„ฐ ๋ฐฐ์—ด๋งŒ์œผ๋กœ๋Š” "์‚ญ์ œํ•˜๊ธฐ"๋ผ๋Š” ํ…์ŠคํŠธ ํ•œ ์ค„๋งŒ ๋นจ๊ฐ„์ƒ‰์œผ๋กœ ๊ทธ๋ฆฐ๋‹ค๊ฑฐ๋‚˜, ์ค‘๊ฐ„์— --- ๊ตฌ๋ถ„์„ (Divider)์„ ๋”ฑ ํ•˜๋‚˜ ๋„ฃ๊ณ  ์‹ถ๋‹ค๋Š” ์ž์ž˜ํ•œ ์ปค์Šคํ…€ UI ์š”๊ตฌ๋ฅผ ๋„์ €ํžˆ ์˜ˆ์˜๊ฒŒ ์ˆ˜์šฉํ•  ์ˆ˜๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

์ด๊ฑธ ์™„๋ฒฝํ•˜๊ฒŒ ํ•ด๊ฒฐํ•ด์ฃผ๋Š” ์•„๋ฆ„๋‹ค์šด HTML ๋‚ด์žฅ ํƒœ๊ทธ๋“ค์„ ์šฐ๋ฆฌ๋Š” ์ด๋ฏธ ๊ฒฝํ—˜ํ•ด๋ดค์Šต๋‹ˆ๋‹ค. ๋ฐ”๋กœ ์ด๊ฒƒ์ž…๋‹ˆ๋‹ค:

<select>
  <option>์งœ์žฅ๋ฉด</option>
  <option selected>์งฌ๋ฝ•</option>
</select>

๋ถ€๋ชจ(<select>)์™€ ์ž์‹(<option>)์ด ํƒœ๊ทธ๋กœ ๋ถ„๋ฆฌ๋˜์–ด ์žˆ์œผ๋ฉด์„œ๋„, ๋งˆ์น˜ ํ•œ ๋ชธ์ฒ˜๋Ÿผ ๋ˆ๋ˆํ•˜๊ฒŒ ๋ถ™์–ด์„œ ํ•˜๋‚˜์˜ ๊ธฐ๋Šฅ(์„ ํƒํ•˜๊ธฐ)์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ์ด ํ˜•ํƒœ๋ฅผ ๋ฆฌ์•กํŠธ๋กœ ๊ทธ๋Œ€๋กœ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ์ด ๋ฐ”๋กœ **์ปดํŒŒ์šด๋“œ ์ปดํฌ๋„ŒํŠธ (Compound Components)**์ž…๋‹ˆ๋‹ค.


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

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

์˜์ฒ ์ด์˜ ๋ฉ”๋‰ด์–ผ์‹ ์™„์ œํ’ˆ ์žฅ๋‚œ๊ฐ:
๊ณต์žฅ์—์„œ ๋กœ๋ด‡ ์žฅ๋‚œ๊ฐ์„ ํ†ต์งธ๋กœ ๊ตณํ˜€์„œ ๋งŒ๋“ค์–ด ์˜ด.
"์ด ๋กœ๋ด‡ ๋จธ๋ฆฌ์— ๋ฆฌ๋ณธ ๋‹ฌ์•„์ค˜"๋ผ๊ณ  ํ•˜๋ฉด ๋กœ๋ด‡ ๊ณต์žฅ ์ž์ฒด๋ฅผ ๋ฐ•์‚ด ๋‚ด๊ณ  ๋„๋ฉด(Props ํŒŒ๋ผ๋ฏธํ„ฐ)์„ ๊ฐœ์กฐํ•ด์•ผ ํ•จ.

์ปดํŒŒ์šด๋“œ ํŒจํ„ด (๋ ˆ๊ณ  ๋ธ”๋ก ์กฐ๋ฆฝ ์„ธํŠธ):

  1. ๋ ˆ๊ณ  ๋ชธํ†ต(Dropdown)
  2. ๋ ˆ๊ณ  ์Šค์œ„์น˜(Toggle)
  3. ๋ ˆ๊ณ  ํŒ”(Item)
    ์œ„ 3๊ฐœ๋ฅผ ๋ฐ•์Šค์— ๋‹ด์•„ ์ถœ์‹œํ•จ.
    ์‚ฌ์šฉ์ž๋Š” ์ด ๋ธ”๋ก ์„ธํŠธ๋“ค์„ ๊ฐ€์ ธ์™€์„œ ์ž๊ธฐ ๋งˆ์Œ๋Œ€๋กœ ์ˆœ์„œ๋ฅผ ๋ฐ”๊พธ๊ฑฐ๋‚˜, ํŒ” ์‚ฌ์ด์— ์Šคํ‹ฐ์ปค(์ปค์Šคํ…€ UI)๋ฅผ ๋ง˜๋Œ€๋กœ ๋ผ์›Œ ๋„ฃ๋Š” ์กฐ๋ฆฝ ๊ณผ์ •์„ ๊ฐ€์ง! ํ•˜์ง€๋งŒ ์–˜๋„ค ์…‹์€ ์–ด์จŒ๋“  ํ•œ ๊ฐ€์กฑ ๋ธ”๋ก์ด๋ผ์„œ, ํŒ”์„ ๋ˆ„๋ฅด๋ฉด ์Šค์œ„์น˜๊ฐ€ ๋˜‘๋”ฑํ•˜๋ฉฐ ๋ฐ˜์‘ํ•˜๋„๋ก ํ•์ค„(Context API)์ด ๋‚ด๋ถ€์ ์œผ๋กœ ๋ณด์ด์ง€ ์•Š๊ฒŒ ํ†ตํ•˜๊ณ  ์žˆ์–ด.

๐Ÿงฉ ๊ทน์˜ ์ฒด๋“: ์‹ค์ „ ์ปดํŒŒ์šด๋“œ ๋ธ”๋ก ๊ตฌ์ถ•ํ•˜๊ธฐ

์šฐ๋ฆฌ์˜ ๋ชฉํ‘œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์•„๋ฆ„๋‹ต๊ฒŒ ์ž‘์„ฑ๋˜๋Š”(Dot Notation ์‚ฌ์šฉ) ๋“œ๋กญ๋‹ค์šด์„ ๋งŒ๋“œ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

// ๐ŸŽฏ ์ตœ์ข… ์ปดํŒŒ์šด๋“œ ๋‹ฌ์„ฑ ๋ชฉํ‘œ (์‚ฌ์šฉํ•˜๋Š” ์ชฝ ์ฝ”๋“œ)
<Dropdown>
  <Dropdown.Toggle>๋ฉ”๋‰ด ์—ด๊ธฐ</Dropdown.Toggle>
  <Dropdown.Menu>
    <Dropdown.Item onClick={goProfile}>๋‚ด ์ •๋ณด</Dropdown.Item>
    <Divider /> {/* ์™€! ๊ตฌ๋ถ„์„ ์„ ๋‚ด ๋งˆ์Œ๋Œ€๋กœ ๋‚„ ์ˆ˜ ์žˆ๋‹ค! */}
    <Dropdown.Item onClick={logout}>
      <span className="text-red">๋นจ๊ฐ„ ๋กœ๊ทธ์•„์›ƒ</span> {/* ์™„์ „ ์ปค์Šคํ…€ ์ž์œ ! */}
    </Dropdown.Item>
  </Dropdown.Menu>
</Dropdown>

โœ… 1. ํ•์ค„ ๋งŒ๋“ค๊ธฐ (๋ณด์ด์ง€ ์•Š๋Š” ๊ณต์œ  State)

์ด ๋ธ”๋ก๋“ค์€ ์„œ๋กœ ๋–จ์–ด์ ธ ์„ ์–ธ๋˜์ง€๋งŒ "๋“œ๋กญ๋‹ค์šด ๋ฌธ์ด ์—ด๋ ธ๋Š”๊ฐ€(isOpen)?" ์ƒํƒœ๋ฅผ ์•Œ์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด ํ†ต๋กœ๋ฅผ ์œ„ํ•ด 11๊ฐ• ๋ฐฉ์‚ฌ๋Šฅ ๊ฒฉ๋ฆฌ๋ฒ•์—์„œ ์ผ๋˜ Context API๋ฅผ ๊บผ๋‚ด ๋“ญ๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„  ๋ถ€๋ชจ ๋ฐ”๋กœ ์•„๋ž˜ ๊ณ„์ธต๋“ค๋งŒ ์“ฐ๋‹ˆ๊นŒ ๋ฐฉ์‚ฌ๋Šฅ ๊ฑฑ์ •์€ ํฌ๊ฒŒ ์•ˆ ํ•ด๋„ ๋ฉ๋‹ˆ๋‹ค!

import { createContext, useContext, useState } from 'react';
 
// 1๏ธโƒฃ ์ด ๋ธ”๋ก๋“ค๋ผ๋ฆฌ๋งŒ ๋ชฐ๋ž˜ ์†Œํ†ตํ•  ๋น„๋ฐ€ ํ…”๋ ˆํŒŒ์‹œ(Context) ์ƒ์„ฑ
const DropdownContext = createContext();
 
function useDropdownContext() {
  const context = useContext(DropdownContext);
  if (!context) throw new Error("Dropdown ํ•˜๋ธ”๋ก๋“ค์€ Dropdown ๋ถ€๋ชจ ์•ˆ์—์„œ๋งŒ ์จ์•ผ ํ•ด!");
  return context;
}

โœ… 2. ๋งˆ๋”์‹ญ(์–ด๋ฏธ) ์ปดํฌ๋„ŒํŠธ ์„ ์–ธ

์ƒํƒœ๋ฅผ ๋ณด๊ด€ํ•˜๊ณ , ํ…”๋ ˆํŒŒ์‹œ ๊ณต๊ฐ„์„ ๊น”์•„์ค„ ๋ถ€๋ชจ ๊ป๋ฐ๊ธฐ์ž…๋‹ˆ๋‹ค.

// 2๏ธโƒฃ ๋Œ€๊ฐ€๋ฆฌ(Wrapper) ์ปดํฌ๋„ŒํŠธ ์ •์˜
export function Dropdown({ children }) {
  const [isOpen, setIsOpen] = useState(false);
  const toggle = () => setIsOpen(prev => !prev);
 
  // ํ…”๋ ˆํŒŒ์‹œ ๋ฐฉํŒŒ๊ณ , ํ•˜์œ„ ๋ ˆ๊ณ  ๋ธ”๋ก๋“ค(children)์„ ๊ทธ๋ƒฅ ์ˆœ์ˆ˜ํ•˜๊ฒŒ ๋žœ๋”๋ง!
  return (
    <DropdownContext.Provider value={{ isOpen, toggle }}>
      <div className="dropdown-wrapper relative">{children}</div>
    </DropdownContext.Provider>
  );
}

โœ… 3. ํ•˜์œ„ ๋ถ€ํ’ˆ(์ž์‹ ํŒŒ์ธ )๋“ค ๋นš์–ด๋‚ด๊ธฐ

์ด์ œ ์กฐ๊ฐ๋“ค์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค. ์ด ์กฐ๊ฐ๋“ค์€ ๋ฐ–์—์„œ ๋งˆ๊ตฌ์žก์ด๋กœ ๋ฐฐ์—ด๋˜๋“  ๋ญํ•˜๋“ , ์–ด๋–ป๊ฒŒ๋“  ํ…”๋ ˆํŒŒ์‹œ ์ฃผํŒŒ์ˆ˜๋กœ ์–ด๋ฏธ์˜ ์ƒํƒœ๋งŒ ๋ฝ‘์•„ ๋จน์œผ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

// 3๏ธโƒฃ ๋ถ€ํ’ˆ 1: ์Šค์œ„์น˜ (๋ˆ„๋ฅด๋ฉด ์œ„์—์„œ ์ง  toggle ํ•จ์ˆ˜ ๋ฐœ๋™)
function Toggle({ children }) {
  const { toggle } = useDropdownContext(); // ํ…”๋ ˆํŒŒ์‹œ ๋ฝ‘๊ธฐ
  return <button className="toggle-btn" onClick={toggle}>{children}</button>;
}
 
// 4๏ธโƒฃ ๋ถ€ํ’ˆ 2: ํŒ์—… ๋ฉ”๋‰ด (๋ฌธ์ด ๋‹ซํ˜€์žˆ์œผ๋ฉด ์ž๊ธฐ ์ž์‹  ํŒŒ๊ดด)
function Menu({ children }) {
  const { isOpen } = useDropdownContext(); // ํ…”๋ ˆํŒŒ์‹œ ๋ฝ‘๊ธฐ
  if (!isOpen) return null; // ๋‹ซํ˜€์žˆ๋„ค? ์•ˆ ๊ทธ๋ ค!
  
  return <div className="dropdown-menu absolute box-shadow">{children}</div>;
}
 
// 5๏ธโƒฃ ๋ถ€ํ’ˆ 3: ๊ฐœ๋ณ„ ๋ฒ„ํŠผ (๋ˆ„๋ฅด๋ฉด ๊ธฐ๋Šฅ ์‹คํ–‰ ํ›„ ๋“œ๋กญ๋‹ค์šด ๋ฌธ ๋‹ซ๊ธฐ)
function Item({ children, onClick }) {
  const { toggle } = useDropdownContext();
  const handleClick = (e) => {
    onClick?.(e); // ํ•  ์ผ ํ•˜๊ณ 
    toggle();     // ์ด ๋…€์„์ด ๋ˆŒ๋Ÿฌ๋„ ๋ฌธ์„ ๋‹ซ๋„๋ก ์•ก์…˜ ํ˜ธ์ถœ!
  };
  return <div className="dropdown-item" onClick={handleClick}>{children}</div>;
}

โœ… 4. ์  ํ‘œ๊ธฐ๋ฒ•(Dot Notation) ์—ฐ๊ฒฐ ํ•ฉ์ฒด

๋งˆ์ง€๋ง‰ ๊ฐ„์ง€(๋ฝ€๋Œ€) ํƒ€์ž„์ž…๋‹ˆ๋‹ค. ์ด ์ปดํฌ๋„ŒํŠธ ๋ธ”๋ก๋“ค์ด ํ•˜๋‚˜์˜ ๊ฐ€์กฑ์ด๋ผ๋Š” ๊ฒƒ์„ ๋ช…์‹œํ•˜๊ธฐ ์œ„ํ•ด, ๋„ค์ด๋ฐ์„ ๋ฌถ์–ด์ค๋‹ˆ๋‹ค.

// Javascript์—์„œ ํ•จ์ˆ˜๋Š” ๊ฐ์ฒด(Object)๋‹ค! ๊ณ ๋กœ ์†์„ฑ์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋‹ค.
Dropdown.Toggle = Toggle;
Dropdown.Menu = Menu;
Dropdown.Item = Item;
 
export default Dropdown;
// ๋! ์ด์ œ Radix UI, Headless UI์—์„œ๋‚˜ ๋ณด๋˜ ๋Œ€๊ธฐ์—…๊ธ‰ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋‚ด ์•ฑ์— ๋“ค์–ด์™”๋‹ค!

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

์ปดํŒŒ์šด๋“œ ํŒจํ„ด์˜ ํ•ต์‹ฌ ์š”์†Œ์—ญํ•  ๋ฐ ์„ค๋ช…
๋ถ€๋ชจ ์ปจํ…Œ์ด๋„ˆ (Wrapper)๊ณตํ†ต์˜ ์ƒํƒœ(State)๋ฅผ ์ฅ๊ณ , ์ž์‹๋“ค์—๊ฒŒ ๋ฟŒ๋ ค์ค„ Context.Provider ์šฐ์‚ฐ์„ ์”Œ์›Œ์ฃผ๋Š” ์ตœ์ƒ๋‹จ ์ปดํฌ๋„ŒํŠธ.
๋น„๋ฐ€ ํ†ต๋กœ (Context API)์ปดํŒŒ์šด๋“œ์˜ ์‹ฌ์žฅ. ์ž์‹ ํŒŒ์ธ ๋“ค์ด ํ…์ŠคํŠธ๋กœ ๋‘˜๋Ÿฌ์‹ธ์ด๋“  ๋ช‡ ๋ށ์Šค๋ฅผ ํ†ต๊ณผํ•˜๋“ , ์–ด๋ฏธ๊ฐ€ ์ง€๋‹Œ isOpen ๊ฐ™์€ ์ƒํƒœ ์กฐ์ž‘๊ถŒ์„ ๋ง˜๊ป ๋ˆ„๋ฆฌ๊ฒŒ ํ•จ.
๊ฐ€๋ณ€ ์ž์‹ ๋ธ”๋ก๋“ค (Children)UI์— ์–ด๋–ป๊ฒŒ ๋ฐ•ํžˆ๋“ , ์ˆœ์„œ๊ฐ€ ๋ฐ”๋€Œ๋“  ๊ฐ์ž์˜ ๊ธฐ๋Šฅ์—๋งŒ ์ถฉ์‹คํ•˜๊ฒŒ ๋™์ž‘ํ•˜๋Š” ์ž‘์€ ์กฐ๊ฐ๋“ค. IoC ์™„์ „ ๋‹ฌ์„ฑ!
์  ํ‘œ๊ธฐ (Dot Notation)Dropdown.Item ์ฒ˜๋Ÿผ ํ•˜๋‚˜์˜ ๋„ค์ž„์ŠคํŽ˜์ด์Šค๋กœ ๋ฌถ์–ด ์ž๋™์™„์„ฑ(Intellisense)๊ณผ ๊ฐ€๋…์„ฑ์„ ๋†’์ด๋Š” ํ•„์‚ด๊ธฐ.

๐Ÿ’ก ํ•œ ์ค„๋กœ ๊ธฐ์–ตํ•˜๊ธฐ
"๋ณต์žกํ•œ ์žฌ์‚ฌ์šฉ UI(๋ชจ๋‹ฌ, ํƒญ, ๋“œ๋กญ๋‹ค์šด)๋ฅผ ๋งŒ๋“ค๋ ค๊ฑฐ๋“  Props์— ํ˜์˜ค์Šค๋Ÿฌ์šด JSON ๋ฐฐ์—ด์„ ์‘ค์…” ๋„ฃ์ง€ ๋ง๊ณ , ์กฐ๊ฐ ์กฐ๊ฐ๋‚œ ๋ ˆ๊ณ  ๋ธ”๋ก ์ปดํŒŒ์šด๋“œ๋กœ ์ฐข์–ด ๋ฐœ๊ฒจ๋ผ."


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

Json ๋ฐฐ์—ด ์ƒ์งœ๋กœ ๋˜์ง€๋Š” ๋ฐฉ์‹๋งŒ ์ฃผ์•ผ์žฅ์ฒœ ํŒ ์—ˆ๋Š”๋ฐ... Radix UI๋‚˜ Headless UI์—์„œ ๋ณด๋˜ ๊ทธ ๊ฐ„์ง€ ๋‚˜๋Š” <Dropdown.Item> ์  ํ‘œ๊ธฐ๋ฒ•์ด ์ด๋ ‡๊ฒŒ ๋งŒ๋“ค์–ด์ง€๋Š” ๊ฑฐ์˜€๋‹ค๋‹ˆ.

๐Ÿ’ก "๋ถ€๋ชจ๋Š” ํ…”๋ ˆํŒŒ์‹œ(Context API)๋ฅผ ์ˆจ๊ธฐ๊ณ , ์ž์‹๋“ค์€ ์กฐ๋ฆฝ๋˜๋Š” ์œ„์น˜ ์ƒ๊ด€์—†์ด ์กฐ์šฉํžˆ ๊ทธ ํž˜์„ ๋นจ์•„๋จน๋Š” ๋ ˆ๊ณ  ๋ธ”๋ก ์ƒํƒœ๊ณ„. ์ด๊ฒƒ์ด ์ปดํŒŒ์šด๋“œ ์ปดํฌ๋„ŒํŠธ๋‹ค!"

์ปดํฌ๋„ŒํŠธ ์•ˆ์— ๋‚จ์˜ HTML์„ ์•„๋ฌด๋ฆฌ ๊ตฌ๊ฒจ ๋„ฃ์–ด๋„ ํ…”๋ ˆํŒŒ์‹œ๋Š” ๋šซ๊ณ  ๋“ค์–ด๊ฐ„๋‹ค๋Š” ๋ง์ด ์ •๋ง ์‹œ์›ํ–ˆ๋‹ค. ๋‹น์žฅ ์‚ฌ๋‚ด ๋””์ž์ธ ์‹œ์Šคํ…œ ์—Ž์„ ๋•Œ ๋‚ด๊ฐ€ '์ด๊ฑด ์ปดํŒŒ์šด๋“œ๋กœ ๊ฐ€์•ผ ํ•ฉ๋‹ˆ๋‹ค'๋ผ๊ณ  ์–ดํ•„ํ•ด ๋ด์•ผ๊ฒ ๋‹ค. ์ด ์งœ๋ฆฟํ•œ ์†๋ง›์„ ์žŠ๊ธฐ ์ „์— ์ง‘์— ๊ฐ€์„œ ํƒญ ์ปดํฌ๋„ŒํŠธ ํ•˜๋‚˜ ์Šคํฌ๋ž˜์น˜๋กœ ์ง์ ‘ ์งœ๋ด์•ผ๊ฒ ๋‹ค.


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

Q1. ๋‹น์‹ ์€ ์˜์ฒ ์ด์˜ ๋”๋Ÿฌ์šด JSON Array ๋“œ๋กญ๋‹ค์šด์„ ์ปดํŒŒ์šด๋“œ ์ปดํฌ๋„ŒํŠธ ํŒจํ„ด์œผ๋กœ ๋ฆฌํŒฉํ† ๋งํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด๋•Œ ๋ฐœ์ƒํ•˜๋Š” ์„ค๊ณ„์ƒ(์•„ํ‚คํ…์ฒ˜)์˜ ๊ฐ€์žฅ ๊ฑฐ๋Œ€ํ•˜๊ณ  ์•„๋ฆ„๋‹ค์šด ์žฅ์  ํ•œ ๊ฐ€์ง€๋ฅผ ๊ณ ๋ฅด์„ธ์š”.

  • A) ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํ•œ ํŒŒ์ผ ๋‚ด์˜ ํ•œ ์ค„๋กœ ์ค„์–ด๋“ค๋ฉด์„œ ๋ฒˆ๋“ค๋ง ์ตœ์ ํ™” ์†๋„๊ฐ€ 300% ํ–ฅ์ƒ๋œ๋‹ค.
  • B) ๋“œ๋กญ๋‹ค์šด ๋‚ด๋ถ€์˜ ํŠน์ • <Item /> ํƒœ๊ทธ ์‚ฌ์ด์— <div>--- ์ ˆ์ทจ์„  ---</div> ๊ฐ™์€ ์™„์ „ํžˆ ์ด์งˆ์ ์ธ UI๋‚˜ ๋ฆฌ์•กํŠธ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ทธ๋ƒฅ ์ƒ์งœ๋กœ ๋ฐ€์–ด ๋„ฃ์–ด๋„ ๊ธฐ์กด ์‹œ์Šคํ…œ(๋ฌธ ๋‹ซํžˆ๊ณ  ์—ด๋ฆผ ์ƒํƒœ ๊ด€๋ฆฌ)์ด 1๋„ ๊นจ์ง€์ง€ ์•Š๋Š” ์™„๋ฒฝํ•œ ํ™•์žฅ์„ฑ(๊ฐœ๋ฐฉ ๋ Œ๋”๋ง).
  • C) Context API๋ฅผ ์“ฐ๊ธฐ ๋•Œ๋ฌธ์— ๋ชจ๋“  ๋ฐ์ดํ„ฐ๊ฐ€ ๋ธŒ๋ผ์šฐ์ €์˜ LocalStorage์— ์˜๊ตฌ ์ €์žฅ๋˜๋Š” ๋งˆ๋ฒ• ๊ฐ™์€ ๋™๊ธฐํ™” ๊ฒฝํ—˜.

โœ… ์ •๋‹ต: B

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค: ์ œ์–ด ์—ญ์ „ ์ฐข๊ธฐ(IoC)์˜ ๊ถ๊ทน๊ธฐ์ธ ์„ค๋ช…์ž…๋‹ˆ๋‹ค. ํ”„๋กญ์Šค๋กœ options=[{}]์‹์œผ๋กœ ๋˜์งˆ ๋•Œ๋Š” ๊ทธ ๋ฐฐ์—ด์— ๋งž๊ฒŒ ๋‚ด๋ถ€์—์„œ for๋ฌธ๊ณผ ๋งˆํฌ์—…์ด ๊ตณ์–ด์ ธ(ํ•˜๋“œ์ฝ”๋”ฉ) ์žˆ์–ด์„œ ์ปค์Šคํ…€ UI๋ฅผ ๋„ฃ๊ธฐ๊ฐ€ ๋ชจ๋ž˜์‹œ๊ณ„ ๊ตฌ๋ฉ ํ†ต๊ณผํ•˜๊ธฐ๋งŒํผ ๋”๋Ÿฌ์› ์ฃ . ๋ฐ˜๋ฉด <Menu> ์•ˆ์— <Item/> ๋ ˆ๊ณ ๋ฅผ ์กฐ๋ฆฝํ•˜๋‹ค๊ฐ€ ์ค‘๊ฐ„์— ๋นจ๊ฐ„ ์ค„ CSS๋ฅผ ๋ฐ•๋“ , ๊ฑฐ๋Œ€ํ•œ ๋‚ด ์ •๋ณด ๋ฐ•์Šค UI๋ฅผ ์„ž์—ฌ ๋ฐ•๋“  ๋ฆฌ์•กํŠธ๋Š” ๊ทธ๋ƒฅ ๊ฐ€๋ณ๊ฒŒ ๋ Œ๋”๋งํ•ด ์ค๋‹ˆ๋‹ค. ๊ป๋ฐ๊ธฐ๋งŒ ์ค„ ๋ฟ ๋‚ด๋ถ€ ์ฑ„์šฐ๊ธฐ๋Š” ์‚ฌ์šฉ์ž ๋ง˜์ธ ์™„๋ฒฝํ•œ ํ™•์žฅ์„ฑ์„ ๊ฐ€์ง‘๋‹ˆ๋‹ค.

Q2. ์ปดํŒŒ์šด๋“œ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋“ค ๋•Œ, ์™œ ๊ตณ์ด ๋ถ€๋ชจ(Dropdown)๊ฐ€ ์ƒํƒœ(isOpen)์™€ toggle ํ•จ์ˆ˜๋ฅผ ์ž์‹๋“ค(Toggle, Item)์—๊ฒŒ props๊ฐ€ ์•„๋‹Œ Context API ํ…”๋ ˆํŒŒ์‹œ๋กœ ๋ฟŒ๋ ค๋Œ€๋ฉฐ ์ง•๊ทธ๋Ÿฝ๊ฒŒ ์šฐํšŒํ•˜๋Š” ์ˆ˜๊ณ ๋ฅผ ํ• ๊นŒ์š”?

// ๋ถ€๋ชจ
<Dropdown>
   {/* props๊ฐ€ ์•„๋‹ˆ๋ผ Context๋กœ ์ž์‹์˜ ์ž์‹์˜ ์ž์‹์ผ์ง€๋ผ๋„ ๊ด€ํ†ตํ•ด์„œ ์ด์ค€๋‹ค! */}
   <div className="layout-box">
     <Dropdown.Toggle>์—ด๊ธฐ</Dropdown.Toggle>
   </div>
</Dropdown>
  • A) ํด๋ž˜์Šค ๊ธฐ๋ฐ˜ React ์ƒ๋ช…์ฃผ๊ธฐ API๊ฐ€ ๋” ์ด์ƒ props๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์—.
  • B) children ์‚ฌ์ด์‚ฌ์ด์— ์œ ์ €๊ฐ€ ์ž๊ธฐ ๋ง˜๋Œ€๋กœ ๊นŠ์€ <div> ํƒœ๊ทธ๋“ค์ด๋‚˜ ๋ž˜ํผ ๊ป๋ฐ๊ธฐ ๋ ˆ์ด์•„์›ƒ์„ ๊ฒน๊ฒน์ด ๋ผ์›Œ ๋„ฃ์„ ์ˆ˜ ์žˆ๋Š”๋ฐ, ์ง์ ‘ props๋กœ ๋‚ด๋ฆฌ๋ฉด ์ด ๋ผ์–ด๋“  ์žฅ์• ๋ฌผ ๋•Œ๋ฌธ์— Props Drilling์ด ๊ฝ‰ ๋ง‰ํ˜€๋ฒ„๋ฆฌ๊ธฐ ๋•Œ๋ฌธ.
  • C) ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ(TypeScript)์—์„œ children์— prop์„ ๊ฐ•์ œ๋กœ ์‘ค์…”๋„ฃ์œผ๋ฉด ์ฆ‰์‹œ ๋Ÿฐํƒ€์ž„ ์—๋Ÿฌ any type never๋ฅผ ๋ฟœ์–ด๋‚ด๊ธฐ ๋•Œ๋ฌธ.

โœ… ์ •๋‹ต: B

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค: ๋งŒ์•ฝ Context๋ฅผ ์•ˆ ์“ด๋‹ค๋ฉด ์˜›๋‚ (๊ณผ๋„๊ธฐ) ๋ฐฉ์‹์ธ React.Children.map ๊ณผ cloneElement๋ผ๋Š” ์•…๋ช… ๋†’์€ ๊ตฌ์‹ ํ•จ์ˆ˜๋ฅผ ์ด์šฉํ•ด ์–ต์ง€๋กœ ๋ถ€๋ชจ ํ”„๋กญ์Šค๋ฅผ ๋ผ์›Œ ๋„ฃ์–ด์•ผ ํ–ˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ด ์˜› ๋ฐฉ์‹์€ ์ค‘๊ฐ„์— ๊ป๋ฐ๊ธฐ ๋ ˆ์ด์•„์›ƒ ๋ž˜ํ•‘ <div>๊ฐ€ ๋ผ์–ด๋“ค๋ฉด ๊ตฌ๋ฉ์ด ๋Š์–ด์ ธ ์ž‘๋™์„ ์•ˆ ํ•˜๋Š” ์น˜๋ช…์  ๋ฒ„๊ทธ๊ฐ€ ํ„ฐ์ง‘๋‹ˆ๋‹ค! ๋ฐ˜๋ฉด Context๋Š” ์•„๋ฌด๋ฆฌ ๊ป๋ฐ๊ธฐ DOM ๋…ธ๋“œ ์ง€ํ•˜๋˜์ „์œผ๋กœ ๊นŠ์ด ์œ ์ €๊ฐ€ ์กฐ๋ฆฝํ•ด ๋“ค์–ด๊ฐ€๋”๋ผ๋„ useContext๋งŒ ๋ฝ‘์•„ ์“ฐ๋ฉด ํ…”๋ ˆํŒŒ์‹œ๋ฅผ ์ฆ‰์‹œ ๊ด€ํ†ต ์ˆ˜์‹ ํ•˜๋ฏ€๋กœ ์™„๋ฒฝํ•œ ์„ค๊ณ„ ์œ ์—ฐ์„ฑ์„ ํ™•๋ณดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Q3. ์ปดํŒŒ์šด๋“œ ์ปดํฌ๋„ŒํŠธ์˜ ๋ํŒ์™• ๋งˆ๊ฐ์น  ๊ธฐ์ˆ ์ธ "์  ํ‘œ๊ธฐ๋ฒ•(Dot Notation)"์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋‹จ์ˆœํžˆ ๋ฉ‹(๊ฐ„์ง€) ๋ชฉ์ ์„ ๋„˜์–ด, ๊ฐœ๋ฐœ ๊ฒฝํ—˜(DX)๊ณผ ํ˜‘์—…์ž๋“ค์—๊ฒŒ ๋‚จ๊ธฐ๋Š” ๊ฐ•๋ ฅํ•œ ์ด์ ์€ ๋ฌด์—‡์ธ์ง€ ์ฃผ๊ด€์‹์œผ๋กœ ์„œ์ˆ ํ•˜์„ธ์š”.

import { Dropdown } from './Dropdown';
 
<Dropdown.Menu>...</Dropdown.Menu>

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

"์  ํ‘œ๊ธฐ๋ฒ•(Dot Notation)์€ ์ปดํฌ๋„ŒํŠธ๋“ค์˜ ์†Œ์†๊ฐ๊ณผ ๋„ค์ž„์ŠคํŽ˜์ด์Šค๋ฅผ ๋ช…ํ™•ํ•˜๊ฒŒ ๋‹จ์†ํ•ด ์ฃผ๋Š” ๊ฐ€๋“œ๋ ˆ์ผ์ž…๋‹ˆ๋‹ค.
์ฒซ์งธ, ํ˜‘์—… ๊ฐœ๋ฐœ์ž๊ฐ€ Dropdown ์ปดํฌ๋„ŒํŠธ๋ฅผ ์น˜๊ณ  .์„ ๋ˆ„๋ฅด๋Š” ์ˆœ๊ฐ„ IDE ์ž๋™์™„์„ฑ(Intellisense)์— ์ด ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํ’ˆ์„ ์ˆ˜ ์žˆ๋Š” ๋ ˆ๊ณ  ๋ถ€ํ’ˆ ๋ชฉ๋ก(Toggle, Item, Menu)์ด ์ซ™ ๋œจ๋ฉด์„œ ๋ฌธ์„œ๋ฅผ ๋ณผ ํ•„์š”์กฐ์ฐจ ์—†์ด ์–ด๋–ป๊ฒŒ ์กฐ๋ฆฝํ•ด์•ผ ํ•˜๋Š”์ง€ ๋ช…์พŒํ•œ ๋‹จ์„œ๋ฅผ ์ค๋‹ˆ๋‹ค.
๋‘˜์งธ, ์ˆ˜๋งŒ ๊ฐœ์˜ ์ˆ˜์ž… ํŒŒ์ผ์ด ๊ตด๋Ÿฌ๋‹ค๋‹ˆ๋Š” ๊ฑฐ๋Œ€ ํด๋”์—์„œ ๊ณ ์ž‘ ์ฐŒ๊บผ๊ธฐ ๋ ˆ๊ณ  ์กฐ๊ฐ๋“ค์ธ Item, Menu๋ผ๋Š”, ๋‹ค๋ฅธ ์•ฑ์—์„œ๋„ ํ”ํ•˜๊ฒŒ ์“ธ ๋ฒ•ํ•œ ๋„ˆ๋ฌด ํ‰๋ฒ”ํ•œ ์ด๋ฆ„๋“ค์ด ์ „์—ญ ํด๋” ๊ณต๊ฐ„๊ณผ Import ์ŠคํŽ˜์ด์Šค๋ฅผ ์ด๋ฆ„ ์ถฉ๋Œ๋กœ ์ž‘์‚ด๋‚ด๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€(ํŒจํ‚ค์ง• ์บก์Аํ™”)ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค."