๐ช 14. Headless ์ปดํฌ๋ํธ (Custom Hooks์ ๋ํ์)
๐ ๊ฐ์
UI๋ ๋จ 1px๋ ๊ทธ๋ฆฌ์ง ์๊ณ , ์ค์ง '๊ธฐ๋ฅ๊ณผ ์ํ(๋)'๋ง ์บก์ํํ์ฌ ๋์์ธ๊ณผ ๋ก์ง์ ์๋ฒฝํ๊ฒ ๋ถ๋จ์ํค๋ ์ต์ ํธ๋ ๋ Headless ํจํด์ ์ ์๋ฅผ ๋ฐฐ์๋๋ค.
๐ฏ ์ด ์น์ ์ ์ฝ๊ณ ๋๋ฉด:
- '๋ชฉ ์๋(Headless) ์ปดํฌ๋ํธ'๋ ๋จ์ด๊ฐ ์๋ฏธํ๋ ์ง์ง ์ ์ฒด(Custom Hooks์ ์งํํ)๋ฅผ ์๋ฒฝํ ์ดํดํ๋ค.
- ๋์์ธ ์๊ตฌ์ฌํญ์ด ํฌ๊ฒ ๋ฌ๋ผ์ ธ๋ ๋ก์ง ์ฝ๋๋ฅผ ํ๋ค์ง ์๋ ๋ถ๋ฆฌ(Separation) ์ค๊ณ๋ฅผ ํ ์ ์๋ค.
- ์ฌ๋ด ๊ณตํต ํ (Hooks)์ ๋ฆฌ๋ทฐํ ๋ UI ์ฑ ์๊ณผ ๋ก์ง ์ฑ ์์ ๊ตฌ๋ถํ ์ ์๋ค.
๐ ๋ชฉ์ฐจ
- ๐ค ์ ์์์ผ ํ๋๊ฐ: UI์ ๋ก์ง์ด ํ ๋ชธ์ผ ๋์ ๋ถ์น๋ณ
- ๐๏ธ ๋น์ ๋ก ๋จผ์ ์ดํดํ๊ธฐ
- ๐งฉ ๊ทน์ ์ฒด๋: ์ค์ ํ์ด๋จธ ํค๋๋ฆฌ์ค ํ ์ด์ ์์
- ๐ (์ฌํ ๋ณด๋์ค) ์์ฑ ์ฃผ์ ๊ธฐ(Prop Getters) ํจํด
๐ ์ด ๋ฌธ์๋ฅผ ์ฝ๊ธฐ ์ ์
โฑ๏ธ ์์ ์ฝ๊ธฐ ์๊ฐ: 10๋ถ / ํต์ฌ ํํธ: 6๋ถ
๐บ๏ธ ์ด ๋ฌธ์์ ๋ฐฐ๊ฒฝ ์ธ๊ณ๊ด: '์์๋ค ์ปค๋ฎค๋ํฐ'
- ์์(๋์์ด๋): "์์ฒ ์จ, ๊ฒฐ์ ํ์ด๋จธ ๋์์ธ์ ํ๋ฉด๋ง๋ค ๋ค๋ฅด๊ฒ ์ฐ๊ณ ์ถ์ด์. ๋ชจ๋ฐ์ผ์์๋ ์ํ ์งํ๋ฅ , ๋ฉ์ธ์์๋ ํฐ ์ซ์, ํค๋์์๋ ์์ ํ ์คํธ๋ก ๋ณด์ฌ์ฃผ๊ณ ์ถ์ด์."
- ์์ฒ (์ ์ ): "ํ์ด๋จธ ๋ก์ง์ด๋ CSS๊ฐ ํ ์ปดํฌ๋ํธ์ ๋ฌถ์ฌ ์์ด์ ํ๋กญ์ ๊ณ์ ๋๋ ค์ผ ํ ๊ฒ ๊ฐ์์. ์ด ๋ฐฉ์์ด๋ฉด ๋ค์ ๊ฐํธ ๋๋ ๊ฐ์ ๋ฌธ์ ๊ฐ ๋ฐ๋ณต๋๊ฒ ๋ค์."
- ์ํธ(๋ฆฌ๋): "์ข์ ์ง๋จ์ด์์. ํต์ฌ ๋ก์ง์ Custom Hook์ผ๋ก ๋นผ๊ณ , UI๋ ์ฌ์ฉํ๋ ์ชฝ์์ ์กฐ๋ฆฝํ๊ฒ ๋ง๋ญ์๋ค. ๊ทธ๊ฒ ํค๋๋ฆฌ์ค(Headless) ํจํด์ ํต์ฌ์ด์์."
๐ค ์ ์์์ผ ํ๋๊ฐ: UI์ ๋ก์ง์ด ํ ๋ชธ์ผ ๋์ ๋ถ์น๋ณ
13๊ฐ์์ ๋ฐฐ์ด ์ปดํ์ด๋ ์ปดํฌ๋ํธ๋ ํ๋ฅญํ์ง๋ง, ์ด์จ๋ <div> ๊ป๋ฐ๊ธฐ ๊ฐ์ ๊ธฐ๋ณธ ๋ ์ด์์๊ณผ DOM ๋ผ๋๋ฅผ ๋ง๋ ์ปดํฌ๋ํธ๊ฐ ์ด๋ ์ ๋ ๋ค๊ณ ์์ด์ผ ํฉ๋๋ค.
ํ์ง๋ง ํ์
์์๋ "๋์(์ฒดํฌ๋ฐ์ค ๊ธฐ๋ฅ, ๋๋๊ทธ ๋๋กญ, ํ์ด๋จธ ๋ฑ)์ ๋๊ฐ์๋ฐ ํ๋ฉด ํํ์ ์ ํ ๋งฅ๋ฝ๋ง๋ค ๋ฌ๋ผ์ง๋" ์๊ตฌ๊ฐ ์์ฃผ ๋์ต๋๋ค.
๐ค ์ ๊น, ๋จผ์ ์๊ฐํด๋ด
"๋น๋ฐ๋ฒํธ ๊ท์น ๊ฒ์ฆ ํผ"์ด ์๋ค๊ณ ์ณ๋ณด์.
ํ์๊ฐ์ ํ์ด์ง์์ ๊ฑฐ๋ํ ํ์ ๋ฐ์ค ์์ ๋นจ๊ฐ ๊ธ์จ๋ก ๊ฒฝ๊ณ ๋ฅผ ๊ทธ๋ฆฌ์ง๋ง, ๋ง์ดํ์ด์ง ๋น๋ฐ๋ฒํธ ์์ ์ฐฝ์์๋ input ๋ฐ์ค ๋ฐ๋ก ์๋ ์์ฃผ ์์โ์์ด์ฝ์ผ๋ก ํํํด์ผ ํด.
๋ ๋ค **"๋น๋ฐ๋ฒํธ๊ฐ 8์ ์ด์์ด๊ณ ํน์๋ฌธ์๊ฐ ํฌํจ๋์๋์ง ์ฒดํฌํ๋ ๋(Logic)"**๋ ๊ฐ์์? ์ด๋ป๊ฒ ๊ทธ ๋๋ง ์ ๋นผ๋จน์ง?
๐๏ธ ๋น์ ๋ก ๋จผ์ ์ดํดํ๊ธฐ
๐ง 5์ด์๊ฒ ์ค๋ช ํ๋ค๋ฉด?
- ์์ฒ ์ด๋ ๋ก๋ด ๊ณต์์ ์ฌ์ฅ๋์ด์ผ. "๊ฑธ์ด ๋ค๋๋ ๋ก๋ด"์ ์ฃผ๋ฌธ๋ฐ์ผ๋ฉด ์ณ๋ฉ์ด๋ฆฌ๋ก ๋ชธ์ฒด๋ ๋ค ๊น๊ณ ๋ชจํฐ๋ ๋ฌ์์ ์์ฑ๋ ์์ ๋ก๋ด์ ๊ณ ๊ฐ์๊ฒ ๊ฑด๋ค์คฌ์ด(์ผ๋ฐ ์ปดํฌ๋ํธ).
- ๊ณ ๊ฐ(์์) ์: "์ฌ์ฅ๋, ๋ค์ ์ฃผ๋ฌธ์! ์ด๋ฒ์ ๊ฑท๋ ์ธํ์ธ๋ฐ... ์ฒซ ๋ฒ์งธ๋ ํฌ๊ทผํ ํ ๋ง, ๋ ๋ฒ์งธ๋ ํฌ๋ช ํ ์ ๋ฆฌ ํ ๋ง, ์ธ ๋ฒ์งธ๋ SF ๋๋์ ์ปค์คํ ํ ๋ง๋ก ๋ง๋ค์ด์ฃผ์ธ์!"
- ์์ฒ ์ด๋ ๋งค๋ฒ ์ ์ธํ์ ํต์งธ๋ก ๋ค์ ๋ง๋ค๋ค ๋ณด๋ ์ ์ ์ง์ณ๊ฐ์ด.
- ์ด๋ ์ํธ ๋ฑ์ฅ: "์ฌ์ฅ๋, ๊ทธ ๋ฐฉ์์ ์ํํด์. ๊ทธ ๊ธฐ๊ณ ๋(CPU ์นฉ)์ ๋ชจํฐ ๊ด์ ๋ผ๋๋ง(Headless) ๋ฑ ์กฐ๋ฆฝํด์ ๋ดํฌ์ ๋ด์ ๋๊ธฐ์ธ์. ๊ฒ ์คํจ, ์์, ๋ฐฐ์น ๊ฐ์ ์ผ๊ตด(Head / UI)์ ๊ณ ๊ฐ์ด ์ง์ ๊ทธ ๋ผ๋ ์์ ์ ํ๋๋ก ๋ง์ด์ฃ !"
์ด๊ฒ์ด Head(์ธํ UI/DOM ์์)๋ฅผ ๋ ๋ ค๋ฒ๋ฆฐ ๋ฌดํ์ ๋ก์ง ์ฝ์ด ์ฅ์น, Headless(ํค๋๋ฆฌ์ค) ํจํด์ ๋๋ค. React ์ํ๊ณ์์ ์ฃผ๋ก Custom Hook ์ด๋ผ๋ ์๋ฒฝํ ์๋จ์ผ๋ก ํํ๋ฅผ ๋ ๊ฒ ๋ฉ๋๋ค.
๐งฉ ๊ทน์ ์ฒด๋: ์ค์ ํ์ด๋จธ ํค๋๋ฆฌ์ค ํ ์ด์ ์์
ํ์ด๋จธ ์ปดํฌ๋ํธ๋ฅผ ์๋ก ๋ค์ด, UI์ ๋ก์ง์ ๋จ๊ณ์ ์ผ๋ก ๋ถ๋ฆฌํด ๋ณด๊ฒ ์ต๋๋ค.
โ 1๋จ๊ณ: UI์ ๋ก์ง์ด ๊ฐํ๊ฒ ๊ฒฐํฉ๋ ์๋ณธ
// ๋์ ์ผ๊ตด์ด ํ ๋ฉ์ด๋ฆฌ๋ก ์ ์ฐฉ๋ ์ปดํฌ๋ํธ (๋์์ธ ๋ณ๊ฒฝ ๋ถ๊ฐ!)
function OldDeathTimer({ initialSeconds }) {
// --- ๐ง ๋(๋ก์ง) ์์ญ ---
const [timeLeft, setTimeLeft] = useState(initialSeconds);
const isDanger = timeLeft <= 5;
useEffect(() => {
// setInterval 1์ด๋ง๋ค ๊น๋จน๋ ๋ก์ง... (๊ธธ์ด์ ์๋ต)
}, []);
// --- ๐ง ๋ ์์ญ ๋ ---
// --- ๐ค ์ผ๊ตด(UI/DOM) ์์ญ ---
// ๋์์ธ์ด ์ํ ์งํ๋ฅ ๋ก ๋ฐ๋๋ฉด ๋ก์ง๊น์ง ํจ๊ป ์์ ํด์ผ ํ๋ค.
return (
<div className="border border-black p-4">
<h2 style={{ color: isDanger ? 'red' : 'black' }}>
๋จ์ ์๊ฐ: {timeLeft}์ด!
</h2>
<button onClick={() => setTimeLeft(initialSeconds)}>๋ฆฌ์
</button>
</div>
);
// --- ๐ค ์ผ๊ตด ์์ญ ๋ ---
}โ 2๋จ๊ณ: ๋ก์ง๋ง Custom Hook์ผ๋ก ๋ถ๋ฆฌํ๊ธฐ (Headless)
๋จผ์ ํ๋ฉด์ ๊ทธ๋ฆฌ๋ JSX๋ฅผ ๊ฑท์ด๋ด๊ณ , ํ์ด๋จธ ์ํ์ ์กฐ์ ํจ์๋ง ๋ฐํํ๋ ํ ์ ๋ง๋ ๋ค.
// ๐ฏ Headless Hook: ์ค์ง ๊ธฐ๋ฅ(ํ์ด๋จธ)๊ณผ ์ํ(๋จ์ ์ด)๋ง ๊ด๋ฆฌํ๋ ์์ ๋
export function useCountdownTimer(initialSeconds) {
const [timeLeft, setTimeLeft] = useState(initialSeconds);
const isDanger = timeLeft <= 5;
// setInterval ๋ก์ง (์๊น๋ ๊ฐ์)
useEffect(() => { ... }, []);
const resetTimer = () => setTimeLeft(initialSeconds);
// UI(div, span)๋ ๋ฆฌํดํ์ง ์๋๋ค.
// ๊ณ์ฐ๋ ์ํ์ ์กฐ์ ํจ์๋ง ๋๊ฒจ์ ํ๋ฉด์ ํธ์ถ์๊ฐ ๊ฒฐ์ ํ๊ฒ ํ๋ค.
return {
timeLeft,
isDanger,
resetTimer
};
}โ 3๋จ๊ณ: ๊ฐ ํ๋ฉด์ด ์ํ๋ UI๋ก ์กฐ๋ฆฝํ๊ธฐ
์ด์ ๊ฐ ํ๋ฉด์ ๊ฐ์ useCountdownTimer ๋ก์ง์ ๊ฐ์ ธ์ค๋, ์์ ์๊ฒ ๋ง๋ ๋งํฌ์
๊ณผ ์คํ์ผ์ ์ ํํ๋ค.
// UI 1: ๊ด๋ฆฌ์์ฉ ํ
์คํธ ํ์ด๋จธ
function AdminTextTimer() {
const { timeLeft, resetTimer } = useCountdownTimer(60);
return (
<div>
<p>[๊ด๋ฆฌ์ ๊ฒฝ๊ณ ] ์ธ์
๋ง๋ฃ๊น์ง {timeLeft}s ๋จ์.</p>
<button onClick={resetTimer}>์ฐ์ฅํ๊ธฐ</button>
</div>
);
}
// UI 2: ๋ชจ๋ฐ์ผ์ฉ ์ํ ํ์ด๋จธ
function MagicBubbleTimer() {
const { timeLeft, isDanger, resetTimer } = useCountdownTimer(60);
return (
<div className={`rounded-full p-10 ${isDanger ? 'bg-red-500' : 'bg-blue-300'}`}>
<span className="text-4xl text-white drop-shadow-lg">
{timeLeft}
</span>
<div onClick={resetTimer} className="hover:scale-110 cursor-pointer">
โจ ๋ง๋ฒ ์ด๊ธฐํ
</div>
</div>
);
}์ด์ isMobileCircle, isAdminText ๊ฐ์ ํํ์ฉ ํ๋กญ์ ๊ณ์ ์ถ๊ฐํ์ง ์์๋ ๋๋ค. ํ์ด๋จธ ๊ท์น์ ํ
์ด ์ฑ
์์ง๊ณ , ํ๋ฉด์ ๊ตฌ์กฐ์ ์คํ์ผ์ ํธ์ถ์๊ฐ ์ฑ
์์ง๋ค. ์ด ๋ถ๋ฆฌ๊ฐ ํค๋๋ฆฌ์ค ํจํด์ ์ค๋ฌด ๊ฐ์น๋ค.
๐ (์ฌํ ๋ณด๋์ค) ์์ฑ ์ฃผ์ ๊ธฐ(Prop Getters) ํจํด
ํด์ธ ์ ๋ช
Headless UI ๋ผ์ด๋ธ๋ฌ๋ฆฌ(Tanstack Table, Downshift ๋ฑ)๋ฅผ ์ดํด๋ณด๋ฉด, ์ ๋ฌดํ์ ๋๊ฐ ๊ทธ๋ฅ timeLeft ์ซ์ ๊ฐ๋ง ๋๊ฒจ์ฃผ๋ ๊ฑธ ๋์ด, "์ฌ์ฉ์๊ฐ UI ํ๊ทธ๋ฅผ ๋ง๋ค ๋ ์ด๋ฐ ์ ๊ทผ์ฑ(aria) ์์ฑ์ด๋ onClick ์ด๋ฒคํธ ํธ๋ค๋ฌ๋ฅผ ๋น ํธ๋ฆฌ์ง ๋ง๊ณ ๊ผญ ๊ฐ์ด ๋ฃ์ผ์ธ์" ํ๊ณ ์ต์
๋ฌถ์(props ๊ฐ์ฒด)์ ํต์งธ๋ก ๋๊ฒจ์ฃผ๋ ๊ธฐ๋ฒ์ ์๋๋ค.
// ๋(Hook) ์์ ์งํ: Prop Getters ๋ฐ์ฌ๊ธฐ
function useDropdownHeadless() {
const [isOpen, setIsOpen] = useState(false);
// ์ด ๋ฌดํ์ ๋๊ฐ, ๋ฏธ๋์ ๊ป๋ฐ๊ธฐ๊ฐ ๋ UI์ ์์ฑํ ๋ญ์น๋ฅผ ๋ฏธ๋ฆฌ ๋ง๋ค์ด์ค๋ค.
const getToggleButtonProps = () => ({
"aria-expanded": isOpen, // ์๊ฐ์ฅ์ ์ธ์ฉ ์ํํ์
id: "magic-dropdown-btn",
onClick: () => setIsOpen(!isOpen) // ๋ด๊ฐ ๋ง๋ ํ ๊ธ ํจ์ ๊ฐ์ ๊ฒฐํฉ
});
return { isOpen, getToggleButtonProps };
}
// ์ฌ์ฉ์(UI ์ฐฐํ ์ฅ์ธ)์ ๊ฒฐํฉ
function MyShinyDropdown() {
const { isOpen, getToggleButtonProps } = useDropdownHeadless();
// ๋ฒํผ์ getToggleButtonProps()๊ฐ ๋ฑ์ด๋ธ ๊ฐ์ฒด์ ํค-๊ฐ์ ์ค๋ฅด๋ฅต ํ์ด์ ํฉ๋ฟ๋ฆฐ๋ค!
return (
<>
<button className="shiny-gold-btn" {...getToggleButtonProps()}>
๋์ ํ ๊ธ
</button>
{isOpen && <div>๊น๊ฟ</div>}
</>
);
}์ด๊ฒ ์ ์ ์ฉํ๊ฐ?: UI๋ฅผ ๋ง๋๋ ์ฌ๋์ ๋งํฌ์
๊ณผ ์คํ์ผ์ ์ง์คํ ์ ์๊ณ , ํ
์ค๊ณ์๋ ์ ๊ทผ์ฑ ์์ฑ, ํค๋ณด๋ ์ด๋ฒคํธ, ํด๋ฆญ ํธ๋ค๋ฌ๋ฅผ getButtonProps ์์ ํจ๊ป ์ ๊ณตํ ์ ์๋ค. ์ญํ ์ด ๋๋๋ฉด์๋ ํ์ ๋์์ ๋น ๋จ๋ฆด ๊ฐ๋ฅ์ฑ์ด ์ค์ด๋ ๋ค.
๐ ์ด๋ฒ์ ๋ฐฐ์ด ๋ด์ฉ ์ด์ ๋ฆฌ
| ๊ด์ | ์ผ๋ฐ UI ์ปดํฌ๋ํธ (<Modal /> ๋ฑ) | Headless (Custom Hook) |
|---|---|---|
| ํ์ฒด(DOM ์์) | div, button ๋ฑ์ ๋ฑ์ด๋ (UI ๊ท์) | ์์ JavaScript ๊ฐ์ฒด๋ ํจ์๋ง ๋ฑ์ (return {}) |
| ๋์์ธ ์์ ๋ | ํ์ ์ (Props๋ก ํ๋ฝ๋ฐ์ ๊ฒ๋ง ๋ฐ๊ฟ) | ๋ฌดํ๋ (Infinity). ๋์์ธ ํ๊ทธ๋ฅผ ์ง๋ ์ฌ๋ ๋ง๋๋ก ํฌ์ฅ์ง ๊ฐ์๋ผ์. |
| ์ถ์ฒ ์ฌ์ฉ ๋๋ฉ์ธ | ์ฌ๋ด ๊ณ ์ ๋ฒํผ, ๋ฒ์ฉ ๋ชจ๋ฌ์ฐฝ, ์ค์ผ๋ ํค | ๋ฌ๋ ฅ(Datepicker) ๋ก์ง, ๋๋๊ทธ ๋๋กญ ์ฒ๋ฆฌ, ๋ก์ปฌ์คํ ๋ฆฌ์ง ๋๊ธฐํ ํผ, ์ฝค๋ณด๋ฐ์ค ์ํ ๋ฑ (๋ก์ง์ ๋ณต์กํ๋ฐ ๋ทฐ๋ ํํธํ๋ ๊ฒ๋ค) |
๐ก ํ ์ค๋ก ๊ธฐ์ตํ๊ธฐ
"๋ณด์ด๋ ๊ฒ(UI)๊ณผ ๋ณด์ด์ง ์๋ ์๊ฐ(Logic)์ ๊ฒฐํฉ์ ๋ถ์ํ๋ผ."
์ฌ๋ฌ ํ๊ฒฝ์์ ๋ชจ์๋ง ๋ค๋ฅด๊ณ ๋๊ฐ์ ๊ธฐ๋ฅ์ ๋ฐ๋ณตํด์ ์ง๊ณ ์๋ค๋ฉด, ๋น์ ์ด ์ง๊ธ ๋น์ฅ ๋ฝ์๋ด์ ๊ฒฉ๋ฆฌํด์ผ ํ ๊ฒ์ UI ์กฐ๊ฐ์ด ์๋๋ผ 'Headless Custom Hook' ์นฉ์ ์ค๊ณ๋๋ค.
๐ ๋ง๋ฌด๋ฆฌ ํด์ฆ
Q1. useWindowSize (๋ธ๋ผ์ฐ์ ๊ฐ๋ก ์ธ๋ก ํฝ์
์ ์ก๊ณ ๋ฆฌ์ฌ์ด์ฆ๋ฅผ ์ถ์ ํ๋ ํ
)๋ ์ฐ๋ฆฌ๊ฐ ๋ฐฐ์ด useCountdownTimer ๊ฐ์ ๋ก์ง๋ค์ ๊ตณ์ด React ์ผ๋ฐ ์ปดํฌ๋ํธ(DOM ์์ ๋ฆฌํด)๋ก ๋ง๋ค์ง ์๊ณ 'Headless ํ
'์ผ๋ก ๋ถ๋ฆฌํด์ผ ํ๋ ๊ฐ์ฅ ๊ฒฐ์ ์ ์ธ ์ด์ ๋ ๋ฌด์์ธ๊ฐ์?
- A) ํด๋์ค ๊ธฐ๋ฐ React ์ํ๊ณ์ ์์ฌ๋ฅผ ์ง์ฐ๊ธฐ ์ํด.
- B) ์ด ๋ก์ง๋ค ์์ฒด๋ ๋์ ๋ณด์ด๋ '๊ฐ์์ ํํ(๋์์ธ)'๋ฅผ ๋จ ํ์๊ฐ ์์ผ๋ฉฐ, ์ด๋ ๊ตฌ์์ ๋ถ๋ชจ ์ปจํ ์ด๋๋ , ๊ผฌํฌ๋ฆฌ ๋ง๋จ ํ ์คํธ ํ๊ทธ๋ ์์ ์์ฌ๋ก ๊ทธ ๊ณ์ฐ๋ '๋ฐ์ดํฐ ๊ฐ'๋ง ์ฃผ์ ๋ฐ์์ ์๊ธฐ ์ท์ ๋ง์ถฐ ์ ์ด์ผ ํ๋ ์์ ์ข ์์ฑ ์๋ ๋น์ฆ๋์ค/ํ๊ฒฝ ํจ์์ด๊ธฐ ๋๋ฌธ.
- C) Headless ํ ์ ๋น๋ ๊ณผ์ ์์ ์๋ฒ๋ฆฌ์ค(Serverless) ํด๋ผ์ฐ๋ ํ๊ฒฝ์ผ๋ก ์ปดํ์ผ๋์ด ์ฐ์ฐ ์๋๊ฐ ๋ฌดํ์ ์๋ ดํ๊ธฐ ๋๋ฌธ.
โ ์ ๋ต: B
๐ก ์์ธ ํด์ค: useWindowSize๋ฅผ ์ปดํฌ๋ํธ <WindowSizeReporter>๋ก ๋ง๋ค๊ณ ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ๋ค์ props๋ก ๋ด๋ ค๋ณด๋ด๋ฉด, ํ๋ฉด ๊ตฌ์กฐ๊ฐ ๋ฐ๋ ๋๋ง๋ค ๋ฐ์ดํฐ ์ ๋ฌ ๊ฒฝ๋ก๋ ํ๋ค๋ฆฝ๋๋ค. ํ
์ผ๋ก ๋ง๋ค๋ฉด { width, height } ๋ฐ์ดํฐ๋ง ํ์ํ ์ปดํฌ๋ํธ๊ฐ ์ง์ ๊ฐ์ ธ๋ค ์ธ ์ ์์ด UI ๊ตฌ์กฐ์ ํ๊ฒฝ ๋ก์ง์ ๊ฒฐํฉ๋๊ฐ ๋ฎ์์ง๋๋ค.
Q2. ํค๋๋ฆฌ์ค ํ
ํจํด์ 'Prop Getters(์ต์
๋ญ์น ๋ฐ์ฌ๊ธฐ)' ์ฌํ ๊ธฐ๋ฒ์ ์ฌ์ฉํ ๋, ๋ถ๋ชจ์์ ... (์คํ๋ ๋ ์ฐ์ฐ์) ๋ฌธ๋ฒ์ ํตํด props๋ฅผ ์ฃผ์
ํ๋ ์ฝ๋์ ์๋ ๋ฐฉ์์ ๋ฐ๋ฅด๊ฒ ์ดํดํ ๊ฒ์?
const { getSlotProps } = useSlotMachineHeadless();
<button {...getSlotProps()} />- A) ์๋ฐ์คํฌ๋ฆฝํธ๋ ์ ๊ตฌ๋ฌธ์ ํ์ฉํ์ง ์์ผ๋ฏ๋ก ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ค. ๋ฐ๋์
id={getSlotProps().id}์ฒ๋ผ ์ผ์ผ์ด ์ ์ด์ผ ํ๋ค. - B)
getSlotProps()๋ ์ฌ์ค์ ๋ฆฌ์กํธ ๊ฐ์ ๋ ์์ง์ ์ง์ ์ ์ํ๋ ํดํน ์คํฌ๋ฆฝํธ๋ฅผ ๋ฑ์ด๋ธ๋ค. - C) ํ
์ด ๋ด๋ถ์์ ๊ณ์ฐํ์ฌ ๋ฑ์ด์ค ๊ฐ์ฒด(์:
{ onClick: fn, "aria-hidden": true })์ ์์ฑ ํค์ ๊ฐ๋ค์ ๋ฆฌ์กํธ JSX ํ๊ทธ์ ์๋ง์ ํ๋กญ์ค ํ๋ผ๋ฏธํฐ๋ค๋ก ์ค๋ฅด๋ฅต ํด์ฒดํ์ฌ ํ ๋ฐฉ์ ์ ๋ถ ์ ๋ฌํด ์ฃผ๋ ์๋ฐ์คํฌ๋ฆฝํธ ์ต๊ฐ์ ๊ฐ์ฒด ๋ฐ์ธ๋ฉ ๋ง๋ฒ์ด๋ค.
โ ์ ๋ต: C
๐ก ์์ธ ํด์ค: Spread Attributes ๋ฌธ๋ฒ์ ๋ง๋ฒ์
๋๋ค. ์ด ๋ฌธ๋ฒ ๋๋ถ์ ํค๋๋ฆฌ์ค ํ
์ฐฝ์กฐ์๋ ๋ด๋ถ ์ ๊ทผ์ฑ(a11y) ์์ฑ์ด 20๊ฐ๋ , ๋ง์ฐ์ค ์ด๋ฒคํธ๊ฐ 5์ข
๋ฅ๋ ๊ทธ๋ฅ ํฐ ๊ฐ์ฒด ๋ญ์น ํ๋๋ฅผ ๋ฑ์ด๋ฒ๋ฆฌ๋ฉด ๋๋ฉ๋๋ค. UI๋ฅผ ์
ํ๋ ์ฌ์ฉ์(Front-end Dev) ์ธก์ ์์ ์ด ๋์์ธํ ์ด์งํธ ํ ํฉ๊ธ ๋ฒํผ ๊ป๋ฐ๊ธฐ๋ฅผ ๋ง๋ค์ด ๋๊ณ ๊ทธ ๊ณณ์ ...getSlotProps() ์ด ํฌ์ฅ์ง ํด์ฒด ์ฃผ๋ฌธ ๋ฑ ํ๋๋ฅผ ์ธ์ฐ๊ธฐ๋ง ํ๋ฉด, ํ
์ด ์ง๋ ๊ฑฐ๋ฏธ์ค ๊ฐ์ 25๊ฐ์ ์์ฑ๊ณผ ํจ์๋ค์ด ์์๊ฐ์ ์ ๊ป๋ฐ๊ธฐ ์์ ฏ ์์ผ๋ก ๊ฐ์ ๋ก ์ค๋ฉฐ๋ค์ด ์ ์ฐฉ(Bind)๋๋ ๊ธฐ์ ์ ๋ฐํํฉ๋๋ค. DX(๊ฐ๋ฐ ๊ฒฝํ)์ ์ ์ ์ด๋ผ ๋ถ๋ฆฌ๋ ๊ตฌ์กฐ์ฃ .
Q3. ์ํธ๊ฐ ํค๋๋ฆฌ์ค(Headless) ๊ตฌ์กฐ๋ก ๋ฌ๋ ฅ(Datepicker)์ ์๋ ์ฝ์ด ๋๋ฅผ ๋ถ๋ฆฌํด๋์์ ๋, ๋ค์ ์ค ์ด '๋ถ๋ฆฌ ์ํคํ ์ฒ' ๋๋ถ์ ํ์ฌ ์ ์ฒด์ ์ผ๋ก ์ป์ ์ ์๋ ์ด๋์ ๋ชจ๋ ์์ ํด ๋ณด์ธ์.
โ ์ ๋ต ๋ฐ ์ฃผ๊ด์ ํด์ค:
- ์ฝ๋ ์ฌ์ฌ์ฉ์ ํ์ฅ: ํ ๋ฒ ์ ์ง๋ Headless Hook์ ๋ฌ๋ ฅ ๊ณ์ฐ ํจ์์ ์ ์ด๋ ๋ก์ง์ PC ์น ๋ฌ๋ ฅ์๋, UI ์ฒด๊ณ๊ฐ ๋ค๋ฅธ ๋ชจ๋ฐ์ผ ํ๋ฉด์๋ ์ฌ์ฌ์ฉํ ์ ์์ต๋๋ค. ํํ ๊ณ์ธต์ด ๋ฐ๋์ด๋ ๋ ์ง ๊ณ์ฐ ๋ก์ง์ ๊ทธ๋๋ก ๊ฒ์ฆ๋ ์ฝ์ด๋ก ๋จ์ต๋๋ค.
- ํ ์คํธ ๊ณ ๋ํ์ ์ฌ์: UI(๋ฒํผ ์์ ๊น๋ฆฌ๊ณ ์ ๋๋ฉ์ด์ ๋๊ณ ..)๋ฅผ ํ ์คํธํ๋ ค๋ฉด ๋ณต์กํ ํ ์คํ ๋๊ตฌ๊ฐ ํ์ํ์ง๋ง, Headless Hook์ ๋ณธ์ง์ด ์์ ๋ก์ง์ด๋ฏ๋ก (๊ฐ์ ๋ฃ์ด์ฃผ๋ฉด ๋ค์ ๋ฌ ๋ฐ์ดํฐ๋ฅผ ์ ๋ฑ๋์ง) ๋จ์ ํ ์คํธ(Unit Test)๋ฅผ ์ง๊ธฐ๊ฐ ๋ฐฑ๋ง ๋ฐฐ๋ ์ฝ๊ณ ๋น ๋ฆ ๋๋ค. UI ๋ณ๋ชฉ ์๋ ์์ ์ฝ์ด์ ํ์์ ๋๋ค!
๐ฃ ์์ฒ ์ด์ ํด๊ทผ ์ผ๊ธฐ
๋์์ธ ๋ฐ๋ ๋๋ง๋ค ์ณ๋ฉ์ด ๋ก๋ด ์ธํผ๋ง ํ์ผ์์ด ๊ฐ์์น์ฐ๋ ๋๋ ๋ค์ด ๋ ์ค๋ฅธ๋ค. ๋ก์ง๊ณผ UI๋ฅผ ๋ถ๋ฆฌํ๋ค๋ ๋น์ ๊ฐ ์ค๋ ๋ด์ฉ์ ํต์ฌ์ ์ ์ก์์คฌ๋ค.
๐ก "UI ๊ป๋ฐ๊ธฐ๋ฅผ ๋ ๋ ค๋ผ(Headless). ์ค์ง ์์ํ ๊ธฐ๋ฅ๊ณผ ๋ฆฌ๋ชจ์ปจ(Hook)๋ง ๋ฑ์ด๋ด๊ณ , ๊ทธ ์ท ์ ํ๋ ๊ฑด ์ฐฐํ ์ฅ์ธ(์ฌ์ฉ์ ์ปดํฌ๋ํธ)์๊ฒ ์๋ฒฝํ ์์ํ๋ผ."
Prop Getters ๊ธฐ๋ฒ ์ด ๊ฑด ์์ ํ ์ถฉ๊ฒฉ์ ์ด์๋ค. ๋ด๊ฐ ์ปดํฌ๋ํธ์ ๋๋ฅผ ์งค ๋, ๋จ๋ค์ด ํด๋ฆญ ํธ๋ค๋ฌ๋ ์ ๊ทผ์ฑ ๊ณ ๋ ค ๋ชปํ ๊ฒ๊น์ง ๋ค ์ฅ์ฐฉํ ์ต์
๋ญํ
์ด๋ฅผ {...getSlotProps()}๋ก ํ ๋ฐฉ์ ์ด์ค๋ค๊ณ ? ์ด๊ฑด ๊ฑฐ์ ์์ ์ ๊ฒฝ์ง๋ค. ๋๋์ด ๋ฆฌ์กํธ ์ค๊ณ์ ์ ์ ์ ๋ดค๋ค๋ ์ฐํ ์ฑ์ทจ๊ฐ์ด ๋ชฐ๋ ค์จ๋ค. ์ํธ ์ ๋ฐฐ ์ต๊ณ !