-
Notifications
You must be signed in to change notification settings - Fork 7
๐ชต 6. ์ข์ ์ปดํฌ๋ํธ๋ ๋ฌด์์ธ๊ฐ? Headless Pattern
ํ๋ก ํธ์๋ ๊ฐ๋ฐ์์๋ ๋ค์ํ ํต์ฌ ๊ณผ์ ๋ค์ด ์กด์ฌํฉ๋๋ค.
-
์ฌ์ฌ์ฉ ๊ฐ๋ฅํ๋ฉด์๋ ์ ์ฐํ ์ปดํฌ๋ํธ ์ค๊ณ
- ๋ณ๊ฒฝ๋๋ ์๊ตฌ์ฌํญ์ ๋์
- ๋ค์ํ ์ํฉ์์์ ์ฌ์ฌ์ฉ์ฑ
- ํ์ฅ ๊ฐ๋ฅํ๊ณ ์ ์ง๋ณด์ ๊ฐ๋ฅํ ๊ตฌ์กฐ
-
์ผ๊ด๋ ๋์์ธ ์์คํ
์ ํจ์จ์ ๊ตฌํ
- ์ผ๊ด๋ ์ฌ์ฉ์ ๊ฒฝํ ์ ๊ณต
- ๊ฐ๋ฐ ์์ฐ์ฑ ํฅ์
- ์ ์ง๋ณด์ ์ฉ์ด์ฑ
-
ํจ๊ณผ์ ์ธ ํ ํ์
๊ณผ ๋ฌธ์ํ
- ์ฝ๋ ํ์ง ์ ์ง
- ํจ์จ์ ์ธ ์์ฌ์ํต
์ด๋ฌํ ๊ณผ์ ๋ค์ ํด๊ฒฐํ๊ธฐ ์ํด Headless Pattern, Tailwind CSS, Storybook์ ์ฑํํ์ต๋๋ค. ์ด๋ฅผ ํจ๊ณผ์ ์ผ๋ก ์ฌ์ฉํ๊ธฐ ์ํด์๋ ๋จผ์ "์ข์ ์ปดํฌ๋ํธ"๊ฐ ๋ฌด์์ธ์ง ์ดํดํ๋ ๊ฒ์ด ์ค์ํฉ๋๋ค.
- ๋จ์ผ ์ฑ ์ ์์น: ์ปดํฌ๋ํธ๋ ํ๋์ ๋ช ํํ ์ญํ ์ ์ง์คํด์ผ ํฉ๋๋ค.
- ์๋ฏธ ์๋ ๋ค์ด๋ฐ: ์ปดํฌ๋ํธ ์ด๋ฆ์ ๊ทธ ์ญํ ๊ณผ ๋ชฉ์ ์ ๋ช ํํ ๋ํ๋ด์ผ ํฉ๋๋ค.
- ๋ช ํํ ์ธํฐํ์ด์ค: ์ ์ ์๋ props์ ์ด๋ฒคํธ
// ๋์ ์์ง๋๋ฅผ ๊ฐ์ง ์ปดํฌ๋ํธ ์์
interface SearchInputProps {
value: string;
onChange: (value: string) => void;
onSubmit: () => void;
}
const SearchInput: React.FC<SearchInputProps> = ({ value, onChange, onSubmit }) => {
// ๊ฒ์ ์
๋ ฅ์ ๊ด๋ จ๋ ๋ก์ง๋ง ํฌํจ
return (
<form onSubmit={onSubmit}>
<input
type="search"
value={value}
onChange={e => onChange(e.target.value)}
/>
</form>
);
};
- ๋ ๋ฆฝ์ ๋์: ๋ค๋ฅธ ์ปดํฌ๋ํธ์ ์์กดํ์ง ์๊ณ ๋ ๋ฆฝ์ ์ผ๋ก ๋์ํด์ผ ํฉ๋๋ค.
- ์ ์ฐํ ํฉ์ฑ: ๋ค์ํ ์ํฉ์์ ์กฐํฉํ ์ ์์ด์ผ ํ๋ฉฐ, ๋ ๋ฆฝ์ ์ธ ์์ ๊ณผ ํ์ฅ์ด ๊ฐ๋ฅํด์ผ ํฉ๋๋ค.
- ์ ์ด์ ์ญ์ : ์ฃผ์ ๊ฒฐ์ ๊ถ์ ์ธ๋ถ๋ก ์์ํ์ฌ ์ฌ์ฌ์ฉ์ฑ์ ๋์ ๋๋ค.
// ๋ฎ์ ๊ฒฐํฉ๋๋ฅผ ๊ฐ์ง ์ปดํฌ๋ํธ ์์
const Card: React.FC<{
renderHeader?: () => React.ReactNode;
renderFooter?: () => React.ReactNode;
children: React.ReactNode;
}> = ({ renderHeader, renderFooter, children }) => {
return (
<div className="card">
{renderHeader && <div className="card-header">{renderHeader()}</div>}
<div className="card-body">{children}</div>
{renderFooter && <div className="card-footer">{renderFooter()}</div>}
</div>
);
};
๋์ ์์ง๋์ ๋ฎ์ ๊ฒฐํฉ๋๋ผ๋ ๋ ๊ฐ์ง ์์น์ ํจ๊ณผ์ ์ผ๋ก ์คํํ๋ ๋ฐฉ๋ฒ ์ค ํ๋๊ฐ ๋ฐ๋ก Headless Pattern์ ๋๋ค.
Headless Component๋ โ๋จธ๋ฆฌ ์๋โ ์ปดํฌ๋ํธ๋ฅผ ์๋ฏธํฉ๋๋ค. ์ฌ๊ธฐ์ โ๋จธ๋ฆฌ(head)โ๋ ์ฌ์ฉ์์๊ฒ ๋ณด์ฌ์ง๋ UI๋ฅผ ์๋ฏธํฉ๋๋ค. ์ฆ, Headless Component๋ UI ๋ ๋๋ง ์ฑ ์์ ๋ถ๋ฆฌํ๊ณ , ์์ํ๊ฒ ๋ก์ง๊ณผ ์ํ ๊ด๋ฆฌ์๋ง ์ง์คํ๋ ์ปดํฌ๋ํธ์ ๋๋ค. ์ด๋ฌํ Headless Component์์๋ UI๋ฅผ ์ธ๋ถ์ ์์ํ๊ฒ ๋ฉ๋๋ค.
UI์ ๋ก์ง์ ๋ถ๋ฆฌํจ์ผ๋ก์จ UI๋ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํด์ง๊ณ , ๋ก์ง์ ๋ค๋ฅธ ์ปดํฌ๋ํธ์ ์ฝ๊ฒ ๊ณต์ ํ ์ ์๊ฒ ๋ฉ๋๋ค.โ
// ๋ก์ง๊ณผ ์ํ ๊ด๋ฆฌ๋ฅผ ๋ด๋นํ๋ hook
const useDropdown = () => {
const [isOpen, setIsOpen] = useState(false);
const toggle = useCallback(() => setIsOpen(prev => !prev), []);
return {
isOpen,
toggle
};
};
// Headless Component ํจํด์ผ๋ก ์ค๊ณ๋ Dropdown ์ปดํฌ๋ํธ
const Dropdown: React.FC<DropdownProps> = ({ children }) => {
// useDropdown ํ
์์ ๋๋กญ๋ค์ด ์ํ์ ํ ๊ธ ๊ธฐ๋ฅ์ ๊ฐ์ ธ์ด
const dropdownProps = useDropdown();
// children ํจ์์ dropdownProps๋ฅผ ์ ๋ฌํ์ฌ ์์ ์ปดํฌ๋ํธ๊ฐ ์ํ๋ฅผ ์ ์ดํ๋๋ก ํจ
return <>{children(dropdownProps)}</>;
};
// ์ค์ ์ฌ์ฉ ์์
const MyDropdown = () => {
return (
<Dropdown>
{({ isOpen, toggle }) => (
<div>
<button onClick={toggle}>๋ฉ๋ด</button>
{isOpen && (
<div>
<div>์ต์
1</div>
<div>์ต์
2</div>
</div>
)}
</div>
)}
</Dropdown>
);
};
์ด๋ฌํ ํจํด์ Input, Button ๋ฑ ๋ค์ํ ์ปดํฌ๋ํธ์๋ ์ ์ฉํ ์ ์์ต๋๋ค.
- UI์ ๋ก์ง์ ๋ช ํํ ๋ถ๋ฆฌ: ๋ก์ง๊ณผ UI๋ฅผ ๋ถ๋ฆฌํจ์ผ๋ก์จ ์ฝ๋๊ฐ ๊น๋ํ๊ณ ์ ์ง๋ณด์๊ฐ ์ฌ์์ง๋๋ค.
- ์ฌ์ฌ์ฉ์ฑ ํฅ์: ๋ก์ง์ ๋ค์ํ UI ์ปดํฌ๋ํธ์์ ์ฌ์ฌ์ฉํ ์ ์์ต๋๋ค.
- ํ ์คํธ ์ฉ์ด์ฑ: UI์ ๋ก์ง์ด ๋ถ๋ฆฌ๋์ด ํ ์คํธ๊ฐ ์ฉ์ดํฉ๋๋ค.
- ๋ณ๊ฒฝ์ ๋ํ ์ ์ฐ์ฑ: UI ๋ณ๊ฒฝ์ด ๋ก์ง์ ์ํฅ์ ๋ฏธ์น์ง ์์ผ๋ฏ๋ก UI ๋ณ๊ฒฝ์ด ๋ ์ ์ฐํ๊ณ ์์ ํฉ๋๋ค.
- ๋ณต์ก์ฑ ์ฆ๊ฐ: UI์ ๋ก์ง์ ๋ถ๋ฆฌํ๋ค ๋ณด๋ฉด ์ฒ์์๋ ๊ตฌ์กฐ๊ฐ ๋ณต์กํด์ง ์ ์์ต๋๋ค.
- ์ค๋ฒ์์ง๋์ด๋ง ์ํ: ๊ฐ๋จํ ๊ธฐ๋ฅ์ ๋ํด ๊ณผ๋ํ ์ถ์ํ๊ฐ ์ด๋ฃจ์ด์ง ์ ์์ต๋๋ค.
- ํ์ต ๊ณก์ : ์๋ก์ด ๊ฐ๋ฐ์๋ ์ด ํจํด์ ์ดํดํ๋ ๋ฐ ์๊ฐ์ด ๊ฑธ๋ฆด ์ ์์ต๋๋ค.
๋ฐ๋ผ์, Headless Pattern์ ๋ณต์กํ UI์ ์์ฃผ ๋ณ๊ฒฝ๋๋ ๋ก์ง์ด ์๋ ์ํฉ์์ ํนํ ์ ์ฉํ๋ฉฐ, ๊ฐ๋จํ ์ปดํฌ๋ํธ๋ ์ํ ๊ด๋ฆฌ์๋ ์คํ๋ ค ๋ณต์ก์ฑ์ ์ฆ๊ฐ์ํฌ ์ ์์ต๋๋ค.
Headless Pattern์ ์ํํธ์จ์ด ๋ณ๊ฒฝ์ ์ ์ฐํ๊ฒ ๋์ํ ์ ์๋ ๋งค์ฐ ํจ๊ณผ์ ์ธ ํจํด์ ๋๋ค. ์ด ํจํด์ ์ํํธ์จ์ด ์์ง๋์ด๋ง์ ๊ธฐ๋ณธ ์์น์ธ ๋์ ์์ง๋์ ๋ฎ์ ๊ฒฐํฉ๋๋ฅผ ์์ฐ์ค๋ฝ๊ฒ ๋ฌ์ฑํ ์ ์๊ฒ ๋์์ค๋๋ค. ํนํ UI/UX๊ฐ ์์ฃผ ๋ณ๊ฒฝ๋๋ ํ๋ ์น ์ ํ๋ฆฌ์ผ์ด์ ๊ฐ๋ฐ์ ์ ํฉํฉ๋๋ค.
ํ์ง๋ง, ๋ชจ๋ ์ํฉ์์ ์ด์์ ์ธ ํด๊ฒฐ์ฑ ์ ์๋๋๋ค. ์ฌ์ฌ์ฉ์ฑ์ด ๋ฎ๊ฑฐ๋, ์ํ ๊ด๋ฆฌ๊ฐ ๊ฐ๋จํ ๊ฒฝ์ฐ์๋ ๋ถํ์ํ ๋ณต์ก์ฑ์ ์ด๋ํ ์ ์์ผ๋ฉฐ, ๊ณผ๋ํ ์ถ์ํ๋ ์ค๋ฒ์์ง๋์ด๋ง์ ์ํ์ด ์กด์ฌํฉ๋๋ค. ๋ํ Hook ์ค๊ณ์ ๋ํ ์ดํด์ ํ์ต์ด ํ์ํ๊ณ , UI์ ๋ก์ง์ ๊ฒฐํฉ๋์ ๋ฐ๋ผ ํ๊ณ๊ฐ ์์ ์ ์์ต๋๋ค.
๋ฐ๋ผ์, ์ด ํจํด์ ๊ทธ ์ ์ฉ์ฑ์ ์ ์ดํดํ๊ณ , ์ ์ ํ ์ํฉ์์ ์ฌ์ฉํ๋ฉด ๋งค์ฐ ๊ฐ๋ ฅํ ๋๊ตฌ๊ฐ ๋ ์ ์์ต๋๋ค.
(๋ฒ์ญ) ํค๋๋ฆฌ์ค ์ปดํฌ๋ํธ: ๋ฆฌ์กํธ UI๋ฅผ ํฉ์ฑํ๊ธฐ ์ํ ํจํด
- 1. ๊ฐ๋ฐ ํ๊ฒฝ ์ธํ ๋ฐ ํ๋ก์ ํธ ๋ฌธ์ํ
- 2. ์ค์๊ฐ ํต์
- 3. ์ธํ๋ผ ๋ฐ CI/CD
- 4. ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์์ด Canvas ๊ตฌํํ๊ธฐ
- 5. ์บ๋ฒ์ค ๋๊ธฐํ๋ฅผ ์ํ ์์ CRDT ๊ตฌํ๊ธฐ
-
6. ์ปดํฌ๋ํธ ํจํด๋ถํฐ ์น์์ผ๊น์ง, ํจ์จ์ ์ธ FE ์ค๊ณ
- ์ข์ ์ปดํฌ๋ํธ๋ ๋ฌด์์ธ๊ฐ? + Headless Pattern
- ํจ์จ์ ์ธ UI ์ปดํฌ๋ํธ ์คํ์ผ๋ง: Tailwind CSS + cn.ts
- Tailwind CSS๋ก ๋์์ธ ์์คํ ๋ฐ UI ์ปดํฌ๋ํธ ์ธํ
- ์น์์ผ ํด๋ผ์ด์ธํธ ๊ตฌํ๊ธฐ: React ํ๊ฒฝ์์ ํจ์จ์ ์ธ ์น์์ผ ์ํคํ ์ฒ
- ์น์์ผ ํด๋ผ์ด์ธํธ ์ฝ๋ ๋ถ์ ๋ฐ ๊ณต์
- 7. ํธ๋ฌ๋ธ ์ํ ๋ฐ ์ฑ๋ฅ/UX ๊ฐ์
- 1์ฃผ์ฐจ ๊ธฐ์ ๊ณต์
- 2์ฃผ์ฐจ ๋ฐ๋ชจ ๋ฐ์ด
- 3์ฃผ์ฐจ ๋ฐ๋ชจ ๋ฐ์ด
- 4์ฃผ์ฐจ ๋ฐ๋ชจ ๋ฐ์ด
- 5์ฃผ์ฐจ ๋ฐ๋ชจ ๋ฐ์ด
- WEEK 06 ์ฃผ๊ฐ ๊ณํ
- WEEK 06 ๋ฐ์ผ๋ฆฌ ์คํฌ๋ผ
- WEEK 06 ์ฃผ๊ฐ ํ๊ณ