๐จ Tailwind 1์ฅ: ์ ํธ๋ฆฌํฐ ํด๋์ค ๋ฉํ ๋ชจ๋ธ
๐ ๊ฐ์
CSS๋ฅผ ํด๋์ค ๋จ์๋ก ์ชผ๊ฐ๋ utility-first ์ฒ ํ โ ์ Tailwind๋ ์ธ๋ผ์ธ ์คํ์ผ์ฒ๋ผ ๋ณด์ด๋๋ฐ๋ ์๋์ด๋ค์ด ์ด๊ดํ๋๊ฐ?
๐ ๋ชฉ์ฐจ
- ๐ ์ด ๋ฌธ์๋ฅผ ์ฝ๊ธฐ ์ ์
- ๐ค ์ ์์์ผ ํ๋๊ฐ
- ๐๏ธ ๋น์ ๋ก ๋จผ์ ์ดํดํ๊ธฐ
- ๐งฉ Utility-First ์ฒ ํ์ด๋ ๋ฌด์์ธ๊ฐ
- โ๏ธ ์ ํต์ ์ธ CSS vs Tailwind CSS
- ๐ป Before / After: ์์๋ค ์ปค๋ฎค๋ํฐ ์นด๋ ์ปดํฌ๋ํธ
- ๐จ ์์ฒ ์ด๊ฐ ํํ ์ ์ง๋ฅด๋ ์ค์๋ค
- ๐ ์ด๋ฒ์ ๋ฐฐ์ด ๋ด์ฉ ์ด์ ๋ฆฌ
- ๐ ๋ง๋ฌด๋ฆฌ ํด์ฆ
- ๐ฃ ์์ฒ ์ด์ ํด๊ทผ ์ผ๊ธฐ
- ๐ ๋ ์์๋ณด๊ธฐ
๐ ์ด ๋ฌธ์๋ฅผ ์ฝ๊ธฐ ์ ์
โฑ๏ธ ์์ ์ฝ๊ธฐ ์๊ฐ: 15๋ถ (์ ์ฒด) / ํต์ฌ ํํธ๋ง: 8๋ถ
๐บ๏ธ ์ด ๋ฌธ์์ ํ๋ฆ
์ ํต CSS ์ ๊ณ ํต โ utility-first ์ ํ์ ๋ฐฐ๊ฒฝ โ Tailwind ํด๋์ค ํด๋ถ โ Before/After ์ค์ ๋น๊ต
๐ฏ ์ด ๋ฌธ์๋ฅผ ๋ค ์ฝ์ผ๋ฉด ํ ์ ์๋ ๊ฒ
- "Tailwind๋ ์ธ๋ผ์ธ ์คํ์ผ์ด๋ ๋ญ๊ฐ ๋ฌ๋ผ์?"๋ผ๋ ์ง๋ฌธ์ ์๋์ด์ฒ๋ผ ๋ต๋ณํ ์ ์๋ค
-
className="flex items-center gap-4 rounded-xl bg-white p-6 shadow-lg"๊ฐ์ ํด๋์ค ๋์ด์ด ์ ์ฌ๋ฐ๋ฅธ ์ค๊ณ์ธ์ง ์ค๋ช ํ ์ ์๋ค - BEM, CSS Modules, Tailwind ์ค ์ํฉ์ ๋ฐ๋ผ ๋ฌด์์ ์จ์ผ ํ ์ง ํ๋จํ ์ ์๋ค
๐บ๏ธ ์ด ๋ฌธ์์ ๋ฐฐ๊ฒฝ ์ธ๊ณ๊ด: '์์๋ค ์ปค๋ฎค๋ํฐ'
- ๐ฃ ์์ฒ (์ ์ ): "๋ฆฌ๋ ๋! ์ Tailwind ์จ๋ดค๋๋ฐ์, ํด๋์ค๊ฐ HTML ์ ๋๋ฌด ๋ง์ด ๋ฐํ๋ ๊ฒ ๊ฐ์์... ์ ์ด์ ํ์ฅ ๋์ ์ด๊ฒ ์ ์ง๋ณด์ ์ง์ฅ์ด๋ผ๊ณ ํ์ จ๊ฑฐ๋ ์?"
- ๐ฆ ์ํธ (๋ฆฌ๋): "์์ฒ ๋, ๊ทธ ํ์ฅ ๋ถ์ด 2018๋
Tailwind ๋ฅผ ๋ณด์ ๊ฑฐ ์๋๊น์? ์ง๊ธ์ ๋ฌ๋ผ์.
class="flex items-center p-6"์ด 10๊ฐ BEM ํด๋์ค๋ฅผ 5๊ฐ CSS ํ์ผ์ ๋๋ ์ฐ๋ ๊ฒ๋ณด๋ค ํจ์ฌ ์ถ์ ์ด ์ฝ๊ฑฐ๋ ์." - ๐ ์์ (PM): "์ ๋ ์ด๋ค ๋ฐฉ์์ด๋ ๋น ๋ฅด๊ฒ ๋์ค๋ฉด ๋ฉ๋๋ค๋ง... ์ต๊ทผ์ ๋์์ธ ์์ ์์ฒญ์ด ๋ค์ด์ฌ ๋๋ง๋ค CSS ํ์ผ ์ฐพ๋๋ผ ๋ค๋ค ํ๋ค์ดํ๋ ๊ฒ ๊ฐ๋๋ผ๊ณ ์."
- ๐จ ์์ (๋์์ด๋): "์ ๋ ๊ณต์ ๋ฐ์ HTML ์์ ์ด๋ค ์คํ์ผ์ด ์ด๋์ ์ค๋์ง ํ์
์ด ๋๋ฌด ์ด๋ ค์ ์ด์. ํด๋์ค ์ด๋ฆ์ด
card__title--active๊ฐ์ ๊ฑด๋ฐ CSS ํ์ผ์ 10๊ฐ๋ ๋ค์ ธ์ผ ํ๋๋ผ๊ณ ์."
๐ค ์ ์์์ผ ํ๋๊ฐ
์์ฒ ์ด๋ ์ง๊ธ๊ป CSS ๋ฅผ ์ด๋ ๊ฒ ์จ์์ด:
/* styles/card.css */
.card { border-radius: 12px; background: white; padding: 24px; box-shadow: 0 4px 6px rgba(0,0,0,.1); }
.card__title { font-size: 20px; font-weight: 700; color: #111; }
.card__badge { background: #dbeafe; color: #1d4ed8; border-radius: 9999px; padding: 2px 10px; }์ด๊ฑธ 5๊ฐ ์ปดํฌ๋ํธ์ ๊ฑธ์ณ ๋น์ทํ CSS ๋ฅผ ๊ฐ์ ๋ง๋ค๋ค ๋ณด๋ ์ด๋ ์๊ฐ card, post-card, user-card, study-card ๊ฐ์ ๋น์ทํ์ง๋ง ๋ฏธ๋ฌํ๊ฒ ๋ค๋ฅธ ํด๋์ค๋ค์ด ์์ญ ๊ฐ ์๊ฒจ๋ฒ๋ ธ์ด.
์์์ด "์นด๋ ๋ฅ๊ธ๊ธฐ ์ข ์ค์ฌ์ฃผ์ธ์" ๋ผ๊ณ ํ๋ฉด? border-radius ๋ฅผ ์ด๋์ ์ด๋๊น์ง ๋ฐ๊ฟ์ผ ํ ์ง ํ์
ํ๋ ๋ฐ๋ง 30๋ถ์ด ๊ฑธ๋ ค. ์์๋ ๋ฒ์จ "์ ์ด๋ ๊ฒ ์ค๋ ๊ฑธ๋ ค์?" ๋ผ๊ณ ์ฌ์ดํ๊ณ ์๊ณ .
์ด ํผ๋์ ๋๋ด๋ฌ ๋ํ๋ ๊ฒ์ด ๋ฐ๋ก Tailwind CSS ์ utility-first ์ ๊ทผ๋ฒ์ด์ผ.
๐๏ธ ๋น์ ๋ก ๋จผ์ ์ดํดํ๊ธฐ
๐ค ์ ๊น, ๋จผ์ ์๊ฐํด๋ด
๋ ๊ณ ๋ธ๋ก์ผ๋ก ์ง์ ์ง๋ ๋ฐฉ๋ฒ์๋ ๋ ๊ฐ์ง๊ฐ ์์ด. ์ด๋ค ๋ฐฉ๋ฒ์ด ๋ ์ ์ฐํ๊ณ ์ฌ์ฌ์ฉ์ฑ์ด ๋์๊น?
๐งฑ ๋ ๊ฐ์ง ๋ ๊ณ ์ ๋ต
[์ ๋ต A: ๋ฏธ๋ฆฌ ๋ง๋ค์ด์ง ์ฑ ์ธํธ]
๋ ๊ณ ์ ์กฐ์ฌ๊ฐ "์ฑ ํจํค์ง", "์ฐ์ฃผ์ ํจํค์ง" ๋ฅผ ๋ง๋ค์ด์ ํ๋งค. ์๋ฆ๋ต์ง๋ง ๋ค๋ฅธ ํํ๋ฅผ ๋ง๋ค๋ ค๋ฉด ํจํค์ง๋ฅผ ๋ฏ์ด๊ณ ์ณ์ผ ํด.
โ ์ ํต CSS ์ ์ปดํฌ๋ํธ๋ณ ํด๋์ค (.card, .btn-primary, .modal-header) ๋ฐฉ์์ด์ผ. ์ ๋ง๋ค์ด์ ธ ์์ง๋ง, ๋ง์ง ์์ผ๋ฉด ์์ ์ด ํ์ํด.
[์ ๋ต B: ์์ ๋ธ๋ก๋ค]
1x1 ๋ธ๋ก, 2x1 ๋ธ๋ก, ๋ฅ๊ทผ ๋ธ๋ก... ๊ธฐ๋ณธ ๋จ์ ๋ธ๋ก๋ง ์๋ฉ. ์ฒ์์ ์ด์ํ์ง๋ง ๋ฌด์์ด๋ ์์ ๋กญ๊ฒ ์กฐํฉํ ์ ์์ด.
โ Tailwind CSS ์ ์ ํธ๋ฆฌํฐ ํด๋์ค (flex, p-6, rounded-xl, shadow-lg) ๋ฐฉ์์ด์ผ.
๐ก ํ ์ค๋ก ๊ธฐ์ตํ๊ธฐ
์ ํต CSS ๋ ์์ฑ๋ ์ฑ ์ธํธ, Tailwind ๋ ์์ ๋ธ๋ก๋ค์ ์ฐฝ๊ณ ๋ค. ์ฐฝ๊ณ ๊ฐ ์ฒ์์ ๋ณต์กํด ๋ณด์ด์ง๋ง, ๊ฒฐ๊ตญ ๋ ๋ง์ ๊ฒ์ ๋ ๋น ๋ฅด๊ฒ ๋ง๋ค ์ ์์ด.
๐งฉ Utility-First ์ฒ ํ์ด๋ ๋ฌด์์ธ๊ฐ
Tailwind CSS ์ ํต์ฌ ์ฒ ํ์ ๋ฑ ํ๋์ผ:
"CSS ์์ฑ ํ๋ = ํด๋์ค ํ๋"
flex ๋ display: flex; ๋ง ๋ด๋นํด. p-6 ์ padding: 1.5rem; ๋ง ๋ด๋นํด. text-lg ๋ font-size: 1.125rem; ๋ง ๋ด๋นํด.
์ด๋ฐ ๋จ์ผ ์ฑ ์์ ์์์ ํด๋์ค ๋ค์ HTML ์ ์กฐํฉํด์ UI ๋ฅผ ๋ง๋๋ ๋ฐฉ์์ด์ผ.
๐ฌ Tailwind ํด๋์ค ํด๋ถํ
<div class="flex items-center gap-4 rounded-xl bg-white p-6 shadow-lg">
<!-- ์์๋ค ์ปค๋ฎค๋ํฐ ์คํฐ๋ ์นด๋ -->
</div>| ํด๋์ค | ์์ฑ๋๋ CSS | ์ญํ |
|---|---|---|
flex | display: flex | Flexbox ํ์ฑํ |
items-center | align-items: center | ์ธ๋ก ์ค์ ์ ๋ ฌ |
gap-4 | gap: 1rem | ์์ ์์ ๊ฐ๊ฒฉ |
rounded-xl | border-radius: 0.75rem | ๋ฅ๊ทผ ๋ชจ์๋ฆฌ |
bg-white | background-color: rgb(255 255 255) | ํฐ ๋ฐฐ๊ฒฝ |
p-6 | padding: 1.5rem | ๋ด๋ถ ์ฌ๋ฐฑ |
shadow-lg | box-shadow: 0 10px 15px -3px ... | ๊ทธ๋ฆผ์ |
์ด๊ฒ ์ ๋ถ์ผ. ๋ณต์กํ CSS ๊ท์น ์์ด, ํด๋์ค ์ด๋ฆ๋ง ๋ด๋ ์คํ์ผ์ด ๋จธ๋ฆฟ์์ ๊ทธ๋ ค์ ธ.
โ๏ธ ์ ํต์ ์ธ CSS vs Tailwind CSS
์ ํต CSS ์ ์ธ ๊ฐ์ง ๊ณ ํต
1. ๋ค์ด๋ฐ ์ง์ฅ (Naming Hell)
/* ๐ฃ ์์ฒ : ๋๋์ฒด ์ด๊ฒ ๋ง๋ ์ด๋ฆ์ธ๊ฐ..? */
.study-card { }
.study-card-wrapper { }
.study-card-container { } /* wrapper๋ container๋ ๋ญ๊ฐ ๋ฌ๋ผ..? */
.study-card__header { }
.study-card__header--active { } /* BEM ์ง์ฅ */2. ์ค์ฝํ ์ค์ผ (Cascade Nightmare)
/* card.css */
.title { font-weight: bold; }
/* post.css */
.title { color: red; } /* ๐ฑ ์ด๋ .title์ด ์ด๊ธธ์ง ์์ธก ๋ถ๊ฐ */3. ๋ฐ๋ ์ฝ๋ ๊ณตํฌ (Dead CSS)
6๊ฐ์ ์ ์ ๋ง๋ .old-card-badge ํด๋์ค... ์ง๊ธ๋ ์ด๋๊ฐ์ ์ฐ์ด๊ณ ์์๊น? ์๋ฌด๋ ๋ชฐ๋ผ. ์ญ์ ํ๋ค๊ฐ UI ๊ฐ ๊นจ์ง๋ฉด? ์์ํํ
ํผ๋๋ค.
Tailwind ๊ฐ ํด๊ฒฐํ๋ ๋ฐฉ๋ฒ
1. ๋ค์ด๋ฐ ๊ณ ๋ฏผ Zero
ํด๋์ค ์ด๋ฆ์ ๋ง๋ค ํ์๊ฐ ์์ด. bg-blue-500, text-sm, font-bold โ ์ด๋ฆ์ด ์ด๋ฏธ ์ ํด์ ธ ์์ด.
2. ์ค์ฝํ ์ถฉ๋ Zero
flex ๋ ํญ์ display: flex; ์ผ. ์ด๋ ํ์ผ์์ ์จ๋, ์ด๋ ์ปดํฌ๋ํธ์์ ์จ๋.
3. ๋ฐ๋ CSS Zero
์ฌ์ฉํ์ง ์๋ ํด๋์ค๋ ๋น๋ ์ ์๋์ผ๋ก ์ ๊ฑฐ๋ผ. HTML ์ ์๋ ํด๋์ค๋ CSS ๋ฒ๋ค์ ํฌํจ๋์ง ์์.
๐ป Before / After: ์์๋ค ์ปค๋ฎค๋ํฐ ์นด๋ ์ปดํฌ๋ํธ
์์๋ค ์ปค๋ฎค๋ํฐ์ ์คํฐ๋ ๊ทธ๋ฃน ์นด๋๋ฅผ ๋ง๋ ๋ค๊ณ ํด๋ณด์.
โ ์ ํต CSS ๋ฐฉ์ (์์งํ ์ฝ๋)
// ๐ฃ ์์ฒ : "์ด๋ ๊ฒ ํ๋ฉด ๋๊ฒ ์ง? CSS ๋ถ๋ฆฌ๊ฐ ๋ง๋ค๊ณ ๋ฐฐ์ ์ผ๋๊น!"
// StudyCard.tsx
export function StudyCard({ title, memberCount, tag }: Props) {
return (
// className ์ด๋ฆ ์ง๋ ๋ฐ๋ง 5๋ถ...
<div className="study-card">
<div className="study-card__header">
<span className="study-card__tag">{tag}</span>
<h3 className="study-card__title">{title}</h3>
</div>
<div className="study-card__footer">
<span className="study-card__member-count">
๋ฉค๋ฒ {memberCount}๋ช
</span>
</div>
</div>
);
}/* StudyCard.module.css */
/* ๐ฃ ์์ฒ : CSS ํ์ผ ๋ฐ๋ก ๊ด๋ฆฌํ๋ ๊ฒ "๊น๋ํ๋ค"๊ณ ์๊ฐํ๋ ์์ */
.study-card {
background-color: white;
border-radius: 12px;
padding: 20px;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
border: 1px solid #e5e7eb;
display: flex;
flex-direction: column;
gap: 12px;
}
.study-card__header {
display: flex;
flex-direction: column;
gap: 8px;
}
.study-card__tag {
background-color: #dbeafe;
color: #1d4ed8;
font-size: 12px;
font-weight: 600;
padding: 2px 10px;
border-radius: 9999px;
width: fit-content;
}
.study-card__title {
font-size: 18px;
font-weight: 700;
color: #111827;
line-height: 1.4;
}
.study-card__footer {
display: flex;
justify-content: space-between;
align-items: center;
}
.study-card__member-count {
font-size: 14px;
color: #6b7280;
}์ด ์ฝ๋๋: TSX 21์ค + CSS 37์ค = 58์ค, ํ์ผ 2๊ฐ
โ Tailwind CSS ๋ฐฉ์ (ํ๋ก ์ฝ๋)
// ๐ฆ ์ํธ: "์ด๋ ๊ฒ ํ๋ฉด ํ์ผ ํ๋์ ๋ชจ๋ ์คํ์ผ ์๋๊ฐ ๋ด๊ฒจ์.
// CSS ํ์ผ ์ด ํ์ ์์ด HTML๋ง ๋ด๋ UI๊ฐ ๊ทธ๋ ค์ง์ฃ ."
// StudyCard.tsx
export function StudyCard({ title, memberCount, tag }: Props) {
return (
<div className="flex flex-col gap-3 rounded-xl border border-gray-200 bg-white p-5 shadow-md">
<div className="flex flex-col gap-2">
{/* ํ๊ทธ: ํ๋ ๋ฐฐ์ง */}
<span className="w-fit rounded-full bg-blue-100 px-3 py-0.5 text-xs font-semibold text-blue-700">
{tag}
</span>
{/* ์ ๋ชฉ */}
<h3 className="text-lg font-bold leading-snug text-gray-900">
{title}
</h3>
</div>
<div className="flex items-center justify-between">
<span className="text-sm text-gray-500">๋ฉค๋ฒ {memberCount}๋ช
</span>
</div>
</div>
);
}์ด ์ฝ๋๋: TSX ํ์ผ ํ๋์ 18์ค, ํ์ผ 1๊ฐ
๐ ์ง์ ๋น๊ต
| ๊ธฐ์ค | ์ ํต CSS | Tailwind |
|---|---|---|
| ํ์ผ ์ | 2๊ฐ (TSX + CSS) | 1๊ฐ (TSX) |
| ์ฝ๋๋ | 58์ค | 18์ค |
| ๋ค์ด๋ฐ ๊ณ ๋ฏผ | ๋งค์ฐ ๋์ | ์์ |
| ์คํ์ผ ์์น | CSS ํ์ผ (๋ถ๋ฆฌ) | JSX (ํตํฉ) |
| ์ญ์ ์ ๋ฐ๋ ์ฝ๋ | ๋จ์ ์ํ ์์ | ์๋ ์ ๊ฑฐ |
| ์์ ์ ํ์ผ ์ด๋ | ํ์ | ๋ถํ์ |
๐จ ์์ฒ ์ด๊ฐ ํํ ์ ์ง๋ฅด๋ ์ค์๋ค
์ค์ 1: ์ธ๋ผ์ธ ์คํ์ผ์ด๋ ํท๊ฐ๋ฆฌ๊ธฐ
{/* ๐ฃ ์์ฒ : "์ด๊ฒ ์ธ๋ผ์ธ ์คํ์ผ์ด๋ ๋ญ๊ฐ ๋ฌ๋ผ์?" */}
{/* โ ์ธ๋ผ์ธ ์คํ์ผ โ hover, ๋ฐ์ํ ๋ถ๊ฐ! */}
<div style={{ padding: '24px', backgroundColor: 'white', borderRadius: '12px' }}>
{/* โ
Tailwind โ hover:, md:, dark: ๊ฐ์ ๋ณํ(variant) ์ฌ์ฉ ๊ฐ๋ฅ! */}
<div className="p-6 bg-white rounded-xl hover:shadow-lg md:p-8 dark:bg-gray-800">๐ก ํต์ฌ ์ฐจ์ด: ์ธ๋ผ์ธ ์คํ์ผ์ "์ง๊ธ ์ด ์ํ๋ง" ์ ์ฉ. Tailwind ๋
hover:,md:,dark:๊ฐ์ variant(๋ณํ) ์ ๋ถ์ฌ์ ์กฐ๊ฑด๋ถ ์คํ์ผ์ด ๊ฐ๋ฅํด.
์ค์ 2: @apply ๋จ์ฉ
/* โ ์์ฒ ์ ์คํด: "ํด๋์ค๊ฐ ๋๋ฌด ๊ธธ๋ฉด @apply๋ก ๋ฌถ์ผ๋ฉด ๋๊ฒ ์ง!" */
.my-card {
@apply flex flex-col gap-3 rounded-xl border border-gray-200 bg-white p-5 shadow-md;
}{/* ๐ฆ ์ํธ: "์์ฒ ๋, @apply๋ ๋ง์ง๋ง ์๋จ์ด์์.
๊ทธ๋ ๊ฒ ํ๋ฉด Tailwind์ ๊ฐ์ฅ ํฐ ์ฅ์ ์ธ
'์ฝ๋์ ์คํ์ผ์ co-location'์ด ์ฌ๋ผ์ ธ์." */}์ฌ๋ฐ๋ฅธ ํ๋จ ๊ธฐ์ค:
- ๊ฐ์ ํด๋์ค ์กฐํฉ์ด 3๊ณณ ์ด์์์ ๋ฐ๋ณต๋๋ค โ React ์ปดํฌ๋ํธ๋ก ์ถ์ถ
- ์ปดํฌ๋ํธ ์ถ์ถ์ด ๋ถ๊ฐํ ์ํฉ์ด๋ค โ ๊ทธ๋๋ง
@apply๊ณ ๋ ค
์ค์ 3: ํด๋์ค ์ ๋ ฌ ๋ฌด์
{/* ๐ฃ ์์ฒ : ํด๋์ค ์์๋ ์๋ฌด๋๋ ์๊ด์๊ฒ ์ง~ */}
<div className="shadow-md p-5 flex rounded-xl bg-white gap-3 flex-col border border-gray-200">
{/* โ
์ํธ๊ฐ ๊ถ์ฅํ๋ ์์: ๋ ์ด์์ โ ํฌ๊ธฐ โ ๊ฐ๊ฒฉ โ ๋ฐฐ๊ฒฝ โ ํ
๋๋ฆฌ โ ํจ๊ณผ */}
<div className="flex flex-col gap-3 rounded-xl border border-gray-200 bg-white p-5 shadow-md">๐ Prettier +
prettier-plugin-tailwindcss๋ฅผ ์ฌ์ฉํ๋ฉด ํด๋์ค ์ ๋ ฌ์ด ์๋์ผ๋ก ๋๋๊น, ๋ฐ๋์ ์ค์นํด๋์!
๐ ์ด๋ฒ์ ๋ฐฐ์ด ๋ด์ฉ ์ด์ ๋ฆฌ
| ๊ฐ๋ | ํต์ฌ ์์ฝ |
|---|---|
| Utility-First | CSS ์์ฑ ํ๋ = ํด๋์ค ํ๋. ์์์ ๋จ์๋ก ์กฐํฉ |
| vs ์ ํต CSS | ๋ค์ด๋ฐ ์ง์ฅ, ์ค์ฝํ ์ถฉ๋, ๋ฐ๋ ์ฝ๋ ๋ฌธ์ ๋ฅผ ํด๊ฒฐ |
| vs ์ธ๋ผ์ธ ์คํ์ผ | hover/๋ฐ์ํ/๋คํฌ๋ชจ๋ ๊ฐ์ variant ์ ์ฉ ๊ฐ๋ฅ |
| @apply | ๋จ์ฉ ๊ธ์ง. 3๊ณณ ์ด์ ๋ฐ๋ณต ์ React ์ปดํฌ๋ํธ ์ถ์ถ ์ฐ์ |
| ํต์ฌ ์ด์ | ํ์ผ ๊ฐ ์ด๋ ์์ด HTML ๋ง ๋ณด๋ฉด ์คํ์ผ์ด ๊ทธ๋ ค์ง |
๐ ๋ง๋ฌด๋ฆฌ ํด์ฆ
Q1. ์์ฒ ์ด๊ฐ hover ์ํ์์๋ง ๋ฐฐ๊ฒฝ์์ ๋ฐ๊พธ๊ณ ์ถ๋ค. ๋ค์ ์ค ์ฌ๋ฐ๋ฅธ Tailwind ์ฝ๋๋?
โ <div style={{ ':hover': { backgroundColor: 'blue' } }}>
โก <div className="bg-blue-500:hover">
โข <div className="hover:bg-blue-500">
โฃ <div className="bg-blue-500 bg-blue-700">
โ
์ ๋ต: โข hover:bg-blue-500
๐ก ์์ธ ํด์ค:
- ์๋ฆฌ ์ค๋ช
: Tailwind ์ variant ์์คํ
์
{variant}:{utility}ํํ์ผ.hover:bg-blue-500์ Tailwind ๊ฐhover\:bg-blue-500:hover { background-color: ... }๋ผ๋ CSS ๋ฅผ ์์ฑํด์ค. ๋ง์น ํด๋์ค ์ด๋ฆ ์์ ์ํ ์ ๋์ฌ๋ฅผ ๋ถ์ด๋ ๊ฒ์ฒ๋ผ ์ง๊ด์ ์ด์ง. - ์ค๋ต ํผ๋๋ฐฑ: โ ์ ์ธ๋ผ์ธ ์คํ์ผ๋ก hover ๋ฅผ ์ ์ดํ ์ ์์ด. CSS ๋ JS ๊ฐ์ฒด๊ฐ ์๋์ผ. โก ์ฒ๋ผ
bg-blue-500:hover์์๋ Tailwind ๋ฌธ๋ฒ์ด ์๋์ผ. ํญ์ variant ๊ฐ ์์ ์. โฃ ๋ ๋ ๋ฐฐ๊ฒฝ์์ ๋์์ ์ ์ธํ๋ ๊ฑด๋ฐ, CSS ์ฐ์ ์์์ ๋ฐ๋ผ ํ์๊ฐ ์ด๊ธฐ์ง๋ง hover ์ํ ์ ์ด๋ ์ ๋ผ. - ๐ ํต์ฌ ๊ธฐ์ต๋ฒ:
{์ํฉ}:{์คํ์ผ}โ ์ํฉ์ด ๋จผ์ , ์คํ์ผ์ด ๋์ค. "hover ํ ๋ bg-blue-500 ์ค!" ๋ฅผ ์ฝ๋๋ก ์ฐ๋ฉดhover:bg-blue-500.
Q2. ์์๊ฐ "์์ฒ ๋, CSS ํ์ผ์ด ๋๋ฌด ๋ง์์ ์์ ์ด ํ๋ค์ด์. Tailwind ์ฐ๋ฉด ์ด๋ค ์ด์ ์ด ์์ฃ ?"๋ผ๊ณ ๋ฌผ์๋ค. ๊ฐ์ฅ ํต์ฌ์ ์ธ ๋ต๋ณ์?
โ ์ ๋ต: "์คํ์ผ์ด HTML ๊ณผ ๊ณต์กด(co-location)ํด์, ์ปดํฌ๋ํธ ํ์ผ ํ๋๋ง ๋ณด๋ฉด ๋ชจ๋ ์คํ์ผ ์๋๋ฅผ ํ์ ํ ์ ์์ต๋๋ค. CSS ํ์ผ ์ฐพ์ ์ผ์ด ์์ด์."
๐ก ์์ธ ํด์ค:
- ์๋ฆฌ ์ค๋ช : Tailwind ์ ์ต๋ ๊ฐ์ ์ co-location โ ์คํ์ผ์ด HTML/JSX ์ ๊ฐ์ ํ์ผ์ ์์ด์ ์ด๋์ ์ด๋ค ์คํ์ผ์ด ์ค๋์ง ์ถ์ ์ด ํ์ ์์ด. ๋ํ ์ปดํฌ๋ํธ๋ฅผ ์ญ์ ํ๋ฉด ๊ทธ ์์ ํด๋์ค๋ ํจ๊ป ์ฌ๋ผ์ ธ์ ๋ฐ๋ CSS ๊ฐ ์๊ธฐ์ง ์์.
- ์ค๋ต ํผ๋๋ฐฑ: "ํด๋์ค ์๊ฐ ์ค์ด๋ ๋ค"๋ ํ๋ ค. ์คํ๋ ค ํด๋์ค ์๋ ๋์ด๋ ์ ์์ด. ํ์ง๋ง ๊ฐ ํด๋์ค๊ฐ ๋จ์ผ ์ฑ ์์ ์ง๊ธฐ ๋๋ฌธ์ ์ถ์ ๊ณผ ์์ ์ด ์ฌ์์ง๋ ๊ฒ์ด ํต์ฌ์ด์ผ.
- ๐ ํต์ฌ ๊ธฐ์ต๋ฒ: "CSS ํ์ผ = ๋ณด๋ฌผ์ฐพ๊ธฐ". Tailwind ๋ ๋ณด๋ฌผ์ง๋(์คํ์ผ) ๋ฅผ ๋ณด๋ฌผ ์์(์ปดํฌ๋ํธ) ์์ ๊ฐ์ด ๋ฃ์ด๋ฌ.
Q3. ์์ฒ ์ด๊ฐ ๊ฐ์ ์นด๋ ์คํ์ผ์ 10๊ตฐ๋ฐ์์ ์ฐ๊ณ ์์ด์ @apply ๋ก ๋ฌถ์ผ๋ ค๊ณ ํ๋ค. ์ํธ๊ฐ ๋์ ์ ์ํ ์ ๊ทผ๋ฒ์?
โ
์ ๋ต: React ์ปดํฌ๋ํธ๋ก ์ถ์ถํ๋ค โ <StudyCard /> ๊ฐ์ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค์ด ํด๋์ค๋ฅผ ์บก์ํ.
๐ก ์์ธ ํด์ค:
- ์๋ฆฌ ์ค๋ช
: Tailwind ๊ณต์ ๋ฌธ์๋ ๊ฐ์กฐํ๋ ์์น โ "๋ฐ๋ณต์ ์ค์ด๋ ๊ฐ์ฅ ์ข์ ๋ฐฉ๋ฒ์ ์ปดํฌ๋ํธ ์ถ์ถ์ด์ง,
@apply๊ฐ ์๋๋ค."@apply๋ Tailwind ์ co-location ์ด์ ์ ๋ฌด๋๋จ๋ฆฌ๊ณ , ๊ฒฐ๊ตญ ์ ํต CSS ์ ๋จ์ ์ธ "์คํ์ผ์ด ์ด๋ ์๋์ง ๋ชจ๋ฅด๊ฒ ๋ค" ๋ฌธ์ ๋ฅผ ๋ค์ ๋ง๋ค์ด. - ์ค๋ต ํผ๋๋ฐฑ:
@apply๊ฐ ์์ ๋์ ๊ฒ ์ ์๋์ผ. HTML/JSX ๊ฐ ์๋ ์จ๋ํํฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ํตํฉ์ด๋ ๊ธ๋ก๋ฒ base ์คํ์ผ ์์ฑ ์์ ์ ์ ํ ์ธ ์ ์์ด. ํ์ง๋ง ์ปดํฌ๋ํธ ์ค์ฝํ ์์์ ๋ฐ๋ณต์ ์ ๊ฑฐํ๋ ์ฉ๋๋ก๋ ์ฐ์ง ๋ง์. - ๐ ํต์ฌ ๊ธฐ์ต๋ฒ: "๋ฐ๋ณต๋๋ฉด ์ปดํฌ๋ํธ๋ก, ์ปดํฌ๋ํธ๊ฐ ๋ถ๊ฐ๋ฅํ๋ฉด ๊ทธ๋ @apply."
๐ฃ ์์ฒ ์ด์ ํด๊ทผ ์ผ๊ธฐ
์ค๋์ Tailwind ๋ฅผ ์ฒ์ ์ ๋๋ก ํ๋ดค๋ค. ์์งํ ๋งํ๋ฉด, ์์ ์ "ํด๋์ค ๋๋ฌด ๋ง์์ ๋ชป ์ฐ๊ฒ ๋ค" ๊ณ ํฌ๋๊ฑฐ๋ ธ๋ ๊ฒ ์ข ๋ถ๋๋ฝ๋ค.
์ํธ ๋์ด "CSS ํ์ผ์ ๋ณ๋๋ก ๊ด๋ฆฌํ๋ ๊ฒ ๊ผญ ๊น๋ํ ๊ฒ ์๋์์. ๋ณด๋ฌผ์ ๋ฐ๋ก ์จ๊ฒจ๋๋ ๊ฒ๋ฟ์ด์์." ๋ผ๊ณ ํ์ จ๋๋ฐ, ์ด ๋ง์ด ๋จธ๋ฆฟ์์์ ๊ณ์ ๋งด๋๋ค.
๐ก ์ค๋์ ๊ตํ: "๊ธธ์ด ๋ณด์ด๋ ํด๋์ค ๋์ด์ด ์ฌ์ค์ ๊ฐ์ฅ ์ ์งํ ์คํ์ผ ์ ์ธ์ด๋ค. ์จ๊ฒจ์ง CSS ๋ ์๋ค."
์ฌ์ค prettier-plugin-tailwindcss ์ค์นํ๊ณ ๋๋๊น ํด๋์ค ์ ๋ ฌ๋ ์๋์ผ๋ก ๋๋๋ผ. ์ด๊ฑฐ ์ข ๋ ์ผ์ฐ ์์์ผ๋ฉด ์ข์์ ํ
๋ฐ. ์์ผ๋ก Flexbox ๋ Grid ๋ฅผ Tailwind ๋ก ์ด๋ป๊ฒ ์ฐ๋์ง๋ ์ตํ์ผ์ง. ์ค๋ ์ ๋
์ ์นํจ ์์ผ๋จน์ผ๋ฉด์ ์ ํ๋ธ ์ข ๋ด์ผ๊ฒ ๋ค. ๐