๐Ÿ‘ ย  ์กฐ์„ฑ๊ฐœ๋ฐœ์‹ค๋ก
ํ…Œ๋งˆ ๋ณ€๊ฒฝ
ํƒœ๊ทธ
๋ฐฉ๋ช…๋ก

๐ŸŽจ CSS ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๊ณ ๋ฏผํ•˜๋Š” ๋‹น์‹ ์„ ์œ„ํ•ด (+ ์„ฑ๋Šฅ๋น„๊ต)

CSS

October 06, 2024



CSS meme

No CSS ClubํšŒ์›์ด ์•„๋‹ˆ๋ผ๋ฉด ์›นํŽ˜์ด์ง€์˜ ์Šคํƒ€์ผ๋ง์„ ์œ„ํ•ด CSS๋Š” ํ•„์ˆ˜์ ์ด๊ณ  ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค
๊ทธ๋Ÿฌ๋‚˜ ์—ญ์‚ฌ์ ์œผ๋กœ CSS๋Š” ๊ฐœ๋ฐœ์ž๋“ค์—๊ฒŒ ๋งŽ์€ ๊ณ ํ†ต์„ ์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค..
๊ทธ ์—ญ์‚ฌ์— ๋Œ€ํ•ด์„œ๋Š” CSS๋Š” ์™œ ์–ด๋ ค์šด๊ฐ€?์‹œ๋ฆฌ์ฆˆ ๊ธ€์ด ์žฌ๋ฐŒ๊ณ  ์œ ์ตํ•˜๋‹ˆ ํ•œ๋ฒˆ์ฏค ๋ณด์‹œ๋ฉด ์ข‹๊ฒ ์Šต๋‹ˆ๋‹ค.
์ด ๊ธ€ ์“ฐ์‹  ๋ถ„์ด ๊ฝค ์œ ๋ช…ํ•˜์‹  ๋ถ„์ด๋˜๋ฐ, ์ตœ๊ทผ์— ํ•œ๋™์•ˆ velog trending 1์œ„์— ๋จธ๋ฌด๋Š” ์ค‘์ธ ํ”„๋ก ํŠธ์—”๋“œ ๊ด€์‹ฌ์‚ฌ ๋ถ„๋ฆฌ์— ๊ด€ํ•œ ๊ธ€ ๋„ ๋งค์šฐ ์œ ์ตํ•ฉ๋‹ˆ๋‹ค. ์ถ”์ฒœ

์•„๋ฌดํŠผ CSS์—๋Š” ๋ช‡ ๊ฐ€์ง€ ๊ฒฐํ•จ์ด ์กด์žฌํ–ˆ์Šต๋‹ˆ๋‹ค :

  • Global Scope : CSS ๊ทœ์น™์„ ์„ ์–ธํ•˜๋ฉด ์ „์ฒด ๋ฌธ์„œ์— ์˜ํ–ฅ์„ ๋ฏธ์นฉ๋‹ˆ๋‹ค. ์ด ํŠน์„ฑ์€ ํ”„๋กœ์ ํŠธ๊ฐ€ ์ปค์งˆ์ˆ˜๋ก ๋ฌธ์„œ๋ฅผ ๋ฐœ๋””๋”œ ํ‹ˆ ์—†๋Š” ๊ทœ์น™๊ณผ ์„ ํƒ์ž์˜ ์ง€๋ขฐ๋ฐญ์œผ๋กœ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.
  • Specificity : CSS๋Š” ์„ ํƒ์ž์— ์˜ํ•œ ๊ทœ์น™์˜ ์ ์šฉ์— ์šฐ์„ ์ˆœ์œ„๊ฐ€ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค. ๊ทœ์น™์„ ๋‚˜์ค‘์— ์„ ์–ธํ–ˆ๋‹ค๊ณ  ํ•ด์„œ ๊ธฐ์กด ์Šคํƒ€์ผ์„ ๋ฎ์–ด์“ธ ์ˆ˜ ์žˆ๋Š”๊ฒŒ ์•„๋‹Œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ํŠน์„ฑ์€ ๊ฐœ๋ฐœ์ž๋“ค๋กœ ํ•˜์—ฌ๊ธˆ ์ ์  ๋” ๋ณต์žกํ•œ ์„ ํƒ์ž๋ฅผ ๋งŒ๋“ค๊ณ , !important๋‚˜ inline-style๊ฐ™์€ ์šฐํšŒ์ฑ…์— ์ฐŒ๋“ค๊ฒŒ ํ–ˆ์Šต๋‹ˆ๋‹ค

important keyword

ํ”„๋กœ์ ํŠธ๊ฐ€ ์ปค์งˆ์ˆ˜๋ก ์ด ๊ฒฐํ•จ์€ ๋Š˜์–ด๋‚˜๊ณ  ๋ถˆ์–ด๋‚˜์„œ ๊ฐœ๋ฐœ์ž๋“ค์„ ๊ณ ํ†ต์Šค๋Ÿฝ๊ฒŒ ํ–ˆ์Šต๋‹ˆ๋‹ค
์ด๋Ÿฌํ•œ ๋ฌธ์ œ์ ์„ ํ•ด๊ฒฐํ•˜๊ณ  CSS๋ฅผ ํ˜„๋ช…ํ•˜๊ฒŒ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด, ๋‹จ์ˆœํžˆ CSS๋ฅผ ์‚ด์ง ํ™•์žฅํ•˜๋Š” ๋ฐฉ๋ฒ•๋ก ๋ถ€ํ„ฐ ์•„์˜ˆ ์ƒˆ๋กœ์šด ํŒจ๋Ÿฌ๋‹ค์ž„์„ ๊ฐ–๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋‚˜ ํ”„๋ ˆ์ž„์›Œํฌ ๋“ฑ์ด ๋“ฑ์žฅํ•ด์™”์Šต๋‹ˆ๋‹ค.
CSS Modules, SCSS, CSS-in-JS, Utility-First CSS ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ, ๋“ฑ๋“ฑ..

์ด๋Ÿฌํ•œ ๋งŽ์€ ์„ ํƒ์ง€๋“ค ์‚ฌ์ด์—์„œ ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ์ž๋Š” ๋งค๋ฒˆ ์„ ํƒ์˜ ๊ธฐ๋กœ์— ๋†“์ž…๋‹ˆ๋‹ค.
ํ”„๋กœ์ ํŠธ์˜ ํŠน์„ฑ, ํ•จ๊ป˜ ์“ธ ๊ธฐ์ˆ , ์‘์ง‘์„ฑ, ์ƒ์‚ฐ์„ฑ ๋“ฑ.. ๋งŽ์€ ์š”์ธ๋“ค์ด ๊ณ ๋ ค๋˜๋Š”๋ฐ
์ €๋Š” ์ด์— ๋Œ€ํ•ด ๊ณ ๋ฏผํ•˜๊ณ  ์ ์ ˆํ•œ ๊ทผ๊ฑฐ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์–ด๋–ค ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ• ์ง€ ์„ ํƒํ•˜๋ ค๋ฉด ์ด๋ก ์„ ๋„˜์–ด์„œ ๊ฒฝํ—˜์ด ํ•„์š”ํ•˜๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ์–ด์š”
์ด๊ฒƒ๋„ ์จ๋ณด๊ณ , ์ €๊ฒƒ๋„ ์จ๋ณด๊ณ , ๊ทธ ์žฅ๋‹จ์ ์„ ๋ชธ์œผ๋กœ ๋Š๊ปด๋ณด๊ณ , ๊ทธ ๊ฒฝํ—˜๋“ค์„ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๊ฐ€ ๋‚˜์ค‘์— ์ง„์งœ ํ•„์š”ํ•  ๋•Œ ์˜์‚ฌ๊ฒฐ์ •์— ์“ฐ๊ณ ์ž ํ–ˆ์Šต๋‹ˆ๋‹ค

choice

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

vanila CSS

์ด ๋ธ”๋กœ๊ทธ์—๋Š” ์ˆœ์ˆ˜ํ•˜๊ฒŒ CSS๋งŒ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
์ฒ˜์Œ ๋งŒ๋“ค ๋•Œ๋ถ€ํ„ฐ ์ผ๋ถ€๋Ÿฌ ์ƒ๊ฐํ–ˆ์–ด์š”. CSS๋งŒ ์จ๋ณด๋ฉด์„œ, ๋ฌด์Šจ ๋ถˆํŽธํ•จ์ด ์ƒ๊ธฐ๊ณ , ์–ด๋–ค ๋‹ˆ์ฆˆ๊ฐ€ ์ƒ๊ธฐ๋Š”์ง€ ๋Š๊ปด๋ณด๊ณ  ์‹ถ์—ˆ์Šต๋‹ˆ๋‹ค

vanila CSS

Global Scope๋Š” ํ™•์‹คํžˆ ๋ฌธ์ œ์ž…๋‹ˆ๋‹ค.
๋ธ”๋กœ๊ทธ๋Š” ์ œ๊ฐ€ ํ˜ผ์ž ๊ฐœ๋ฐœํ•˜๊ธฐ๋„ ํ•˜๊ณ , ๊ทธ์ •๋„๋กœ ๋งŽ์€ ์Šคํƒ€์ผ ์ฝ”๋“œ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š์•„์„œ ๋‹คํ–‰์ด์ง€, ํ˜‘์—…์ž๊ฐ€ ์žˆ๊ณ  ํ”„๋กœ์ ํŠธ๊ฐ€ ์ปค์งˆ ์ˆ˜๋ก ์ด ์„ ํƒ์ž๋“ค ์‚ฌ์ด์˜ ์ง€๋ขฐ๋ฐญ์„ ํ”ผํ•˜๊ธฐ๋Š” ์‰ฝ์ง€ ์•Š์„๊ฑฐ์˜ˆ์š”

global scope

๊ทธ๋ฆฌ๊ณ  ๋ชจ๋“  ์Šคํƒ€์ผ์ฝ”๋“œ๊ฐ€ ์ค‘์•™ํ™”๋  ์ˆ˜๋ฐ–์— ์—†๊ธฐ ๋•Œ๋ฌธ์—, ์ „ํ™˜ ์‚ฌ์ด์˜ ๊ฑฐ๋ฆฌ๊ฐ€ ๋„ˆ๋ฌด ๋ฉ€์–ด์ง‘๋‹ˆ๋‹ค..
๊ทธ๋ ‡๋‹ค๊ณ  ์Šคํƒ€์ผ์ฝ”๋“œ๋ฅผ ์—ฌ๋Ÿฌ๊ฐœ ๋‘˜ ์ˆ˜๋„ ์—†์ฃ . ํŒŒํŽธํ™”๋˜๊ธฐ ์‹œ์ž‘ํ•˜๋ฉด Global Scope๋ฅผ ๊ฐ๋‹นํ•˜๊ธฐ ๋”์ฐํ•ด์ง‘๋‹ˆ๋‹ค

vanila CSS์—์„œ๋Š” ์กฐ๊ฑด๋ถ€ ์Šคํƒ€์ผ๋ง์„, class๊ฐ™์€ ๊ฒƒ์„ ๋™์ ์œผ๋กœ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜ ์ œ๊ฑฐํ•˜์—ฌ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.
๋งŒ์•ฝ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜์—ฌ body์— darkmode ํด๋ž˜์Šค๋ฅผ ๋„ฃ๊ฑฐ๋‚˜ ์ œ๊ฑฐํ•˜๋ ค๋ฉด, ์•„๋ž˜์ฒ˜๋Ÿผ ํ•ฉ๋‹ˆ๋‹ค.

button.addEventListener("click", () => {
  // body ํƒœ๊ทธ์— darkmode ํด๋ž˜์Šค๋ฅผ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜ ์ œ๊ฑฐ
  document.body.classList.toggle("darkmode");
});

๋˜๋Š” ๋ฆฌ์•กํŠธ ์ƒํƒœ๋ฅผ ์ด์šฉํ•˜์—ฌ ์ผ๋ถ€ ์ปดํฌ๋„ŒํŠธ์— ๋Œ€ํ•ด ๋™์ ์œผ๋กœ ์Šคํƒ€์ผ์„ ์ ์šฉํ•˜๋ ค๋ฉด ์ด๋ ‡๊ฒŒ ๋˜๊ฒ ๋„ค์š”

<button className={isActive ? "active" : undefined}>

CSS Modules

๋Ÿฐํ…Œ์ผ์ด๋ผ๋Š” ์„œ๋น„์Šค๋ฅผ ๊ฐœ๋ฐœํ•  ๋•Œ ์ผ์Šต๋‹ˆ๋‹ค

๊ทธ๋Œ€๋กœ CSS๋ฅผ ์ž‘์„ฑํ•˜๋Š”๊ฑด ๋˜‘๊ฐ™์€๋ฐ, ์ผ์ • ๋ถ€๋ถ„(ํŒŒ์ผ ๋“ฑ)์—๋งŒ ๊ตญํ•œ๋˜๋Š” Local Scope๋ฅผ ๊ฐ€์ง„ CSS์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์–ด์š”. ํŒŒ์ผ๋ช….module.css ์ด๋ ‡๊ฒŒ ์ด๋ฆ„์„ ์ง€์œผ๋ฉด CSS module์ด ๋ฉ๋‹ˆ๋‹ค
์ด๋ฅผ import styles from './my-style.module.css'์™€ ๊ฐ™์ด importํ•˜์—ฌ ์‚ฌ์šฉํ•ด์š”
๊ทธ๋Ÿผ ์ด์ œ CSS Module์€ ์„ ์–ธ๋œ ํด๋ž˜์Šค๋“ค์— ๋Œ€ํ•ด ์ž„์˜์˜ ํ•ด์‹œ๊ฐ’์„ ์ถ”๊ฐ€ํ•˜๋ฏ€๋กœ, class์ด๋ฆ„์ด ๊ฒน์น˜๋”๋ผ๋„ ๋‹ค๋ฅธ ํด๋ž˜์Šค๊ฐ€ ๋˜์–ด ๋ฒ”์œ„๊ฐ€ ์ด๋ฅผ importํ•œ ํŒŒ์ผ์—๋งŒ ํ•œ์ •๋ฉ๋‹ˆ๋‹ค. ์ด ๋•๋ถ„์— CSS์ฝ”๋“œ๋ฅผ ํŒŒ์ผ(์ปดํฌ๋„ŒํŠธ, ํŽ˜์ด์ง€ ๋“ฑ) ๋‹จ์œ„๋กœ ๊ฒฉ๋ฆฌํ•˜๊ณ  ๊ตฌ๋ถ„์ง€์„ ์ˆ˜ ์žˆ์–ด์š”.

/* Button.module.css */
.button {
  background-color: #3498db;
  padding: 10px;
}
// React Component
import styles from "./Button.module.css";

function Button() {
  return <button className={styles.button}>Click Me</button>;
}

CSS Modules์—์„œ ์กฐ๊ฑด๋ถ€๋กœ ์Šคํƒ€์ผ์„ ์ ์šฉํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด
vanila CSS์—์„œ์ฒ˜๋Ÿผ ํด๋ž˜์Šค๋ฅผ ๋™์ ์œผ๋กœ ๋ผ์šฐ๋Š” ๊ฒƒ์œผ๋กœ ํ•ด์š”
className={isActive ? styles.active : undefined} ์ด๋Ÿฐ ์‹์œผ๋กœ์š”

์žฅ๋‹จ์ 

CSS Modules

์ข‹์€ ์ ์€, vanila CSS๋ฅผ ์ž˜ ์•Œ๋ฉด ์ด module๋งŒ์œผ๋กœ๋„ ์ถฉ๋ถ„ํžˆ ํŽธํ•ฉ๋‹ˆ๋‹ค.
์‚ฌ์‹ค์ƒ CSS์™€ ๋‹ฌ๋ผ์งˆ๊ฒŒ ํ•˜๋‚˜๋„ ์—†์œผ๋‹ˆ๊นŒ์š”.
๊ธฐ์กด CSS์—์„œ ์—ด๋ฐ›์•˜๋˜ global scope๋ฅผ ํ•ด๊ฒฐํ•˜๊ณ , ๋•๋ถ„์— ์šฐ์„ ๋„์™€ ๋ณต์žก๋„๊ฐ€ ์ ์  ๊นŠ์–ด์ง€๋Š” ์ƒํ™ฉ๋„ ํ”ผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค
๋˜ํ•œ ์ด์ œ ์ด ๋•๋ถ„์— CSS ์ฝ”๋“œ๋Š” ์ด๋ฅผ ํ•„์š”๋กœํ•˜๋Š” ์ฝ”๋“œ์˜ ๋ฐ”๋กœ ์˜†์— ์œ„์น˜ํ•˜์—ฌ ๋ฌถ์ผ ์ˆ˜ ์žˆ๊ฒŒ ๋ผ์š”

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

์ด์™ธ์— ๋‹ค๋ฅธ ์ž๋ฃŒ๋“ค์—์„œ ๋ณด๋ฉด ์ „์—ญ์Šคํƒ€์ผ๋ง์ด๋‚˜, ํŒŒํŽธํ™”๊ฐ€ ์‹ฌํ•ด์ง„๋‹ค๋Š” ๊ฒƒ์„ ๋‹จ์ ์œผ๋กœ ์ด์•ผ๊ธฐํ•˜๊ธฐ๋„ ํ•˜๋Š”๋ฐ, ์ „ ๋”ฑํžˆ ๊ณต๊ฐํ•˜์ง€๋Š” ์•Š์Šต๋‹ˆ๋‹ค.
์ „์—ญ์Šคํƒ€์ผ๋ง์ด ํ•„์š”ํ•˜๋ฉด ์—ฌ์ „ํžˆ ๋ชจ๋“ˆ์ด ์•„๋‹Œ ์ „์—ญcss์‹œํŠธ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๊ณ , ์šฐ๋ฆฌ๋Š” ๋ชจ๋“ˆํ™”๋ฅผ ์œ„ํ•ด CSS Modules๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์‹œ์ž‘ํ•œ๊ฒ๋‹ˆ๋‹ค

CSS-in-JS

CSS-in-JS๋Š” ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ์ฝ”๋“œ์—์„œ CSS๋ฅผ ์ž‘์„ฑํ•˜๋Š” ํŒจ๋Ÿฌ๋‹ค์ž„์ž…๋‹ˆ๋‹ค.
styled-component๋‚˜ emotion๊ฐ™์€ ๊ฒƒ๋“ค์ด ๋Œ€ํ‘œ์ฃผ์ž์ธ๋ฐ
์ €๋Š” emotion์„ ์ฃผ๋กœ ์‚ฌ์šฉํ•ด๋ดค์Šต๋‹ˆ๋‹ค. ํšŒ์‚ฌ์—์„œ๋„ emotion์œผ๋กœ ์ž‘์„ฑํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๊ฝค ๋งŽ๊ณ ,
์ฒ˜์Œ ๋ฆฌ์•กํŠธ ํ”„๋กœ์ ํŠธ ํ•  ๋•Œ๋„ ์ด๊ฑฐ๋กœ ์‹œ์ž‘ํ–ˆ์–ด์š”.

emotion์€ ์•„๋ž˜์ฒ˜๋Ÿผ ์ž‘์„ฑํ•ด์š”

const RedButton = styled.button`
  background-color: red;
`;

function FooComponent() {
  return <RedButton>START!</RedButton>;
}

์Šคํƒ€์ผ์„ ๊ฐ–๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ƒ์„ฑํ•˜๊ณ , ๋งˆ์น˜ ์œผ๋ ˆ HTML์š”์†Œ๋‚˜ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๋“ฏ์ด ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
์ปดํฌ๋„ŒํŠธ ํŒŒ์ผ์— ํ•จ๊ป˜ ์กด์žฌํ•˜๊ฒŒ ํ•ด๋„ ๋˜๊ณ , ๋”ฐ๋กœ style.jsx๋กœ ์˜†์— ๋ถ„๋ฆฌํ•˜์—ฌ ๋‘ฌ๋„ ๊ดœ์ฐฎ์Šต๋‹ˆ๋‹ค.
์ทจํ–ฅ์— ๋”ฐ๋ผ ๊ฐˆ๋ฆฌ๋Š” ๊ฒƒ ๊ฐ™์•„์š”.

๋™์  ์Šคํƒ€์ผ๋ง์€ ์•„๋ž˜์ฒ˜๋Ÿผ ํ•ฉ๋‹ˆ๋‹ค.

const RedButton = styled.button`
  background-color: ${props => (props.active ? "red" : "gray")};
`;

function FooComponent() {
  const [isActive, setIsActive] = useState(false);
  return (
    <RedButton active={isActive} onClick={() => setIsActive(prev => !prev)}>
      START!
    </RedButton>
  );
}

๋งˆ์น˜ ์ง„์งœ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋“ค๋“ฏ์ด, props๋ฅผ ๋„˜๊ธฐ๊ณ  styled ์ฝ”๋“œ ๋‚ด์—์„œ props๋ฅผ ๋ฐ›์•„ ์“ธ ์ˆ˜ ์žˆ์–ด์š”.
๋˜๋Š” ์•„๋ž˜์ฒ˜๋Ÿผ๋„ ๋ฉ๋‹ˆ๋‹ค.

function FooComponent() {
  const [isActive, setIsActive] = useState(false);
  const buttonStyle = css`
    background-color: ${isActive ? "red" : "gray"};
  `;
  return (
    <button css={buttonStyle} onClick={() => setIsActive(prev => !prev)}>
      START!
    </button>
  );
}

์žฅ๋‹จ์ 

CSS-in-JS ์žฅ๋‹จ์ 

์ด ๋ฐฉ๋ฒ•์€ ์Šคํƒ€์ผ ์ฝ”๋“œ์˜ ์žฌ์‚ฌ์šฉ์ด ๊ฝค ์‰ฝ๊ณ  ์ข‹์•˜์–ด์š”.
๊ทธ๋ฆฌ๊ณ  ์ œ์ผ ์ข‹์€ ์ ์€, ๊ฐ ์ปดํฌ๋„ŒํŠธ๋“ค์— ์ด๋ฆ„์ด ์žˆ์œผ๋‹ˆ๊นŒ ์ฝ”๋“œ ์ˆ˜์ •ํ•  ๋•Œ ์•Œ์•„๋ณด๊ธฐ ์‰ฝ๋‹ค๋Š” ์ ์ž…๋‹ˆ๋‹ค.
์˜ˆ๋ฅผ ๋“ค์–ด TitleWrapper, ContentWrapper ์ด๋Ÿฐ์‹์œผ๋กœ์š”. ์›๋ž˜๊ฐ™์œผ๋ฉด ๊ทธ๋ƒฅ div, div, ... ๋‹ค ๋˜‘๊ฐ™์€ div๊ฒ ๋„ค์š”.
๊ฝค ์ฝ๊ธฐ ์‰ฌ์šด ์ฝ”๋“œ๋ฅผ ๋งŒ๋“ค์–ด์ค๋‹ˆ๋‹ค.

JS ๋ณ€์ˆ˜๋ฅผ ๊ทธ๋Œ€๋กœ ๊ฐ€์ ธ๋‹ค๊ฐ€ ๋™์  ์Šคํƒ€์ผ๋ง์— ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ๋„ ํฐ ์žฅ์ ์ž…๋‹ˆ๋‹ค.
๋ฆฌ์•กํŠธ๋‚˜ JS์ ์ธ ์‚ฌ๊ณ ๋ฐฉ์‹์—์„œ ๋ณ„๋กœ ๋ฉ€๋ฆฌ ๋–จ์–ด์ง€์ง€ ์•Š์•„๋„ ๋œ๋‹ค๋Š” ์ ์—์„œ ์ข‹์Šต๋‹ˆ๋‹ค
๋‹ค๋ฅธ ๋ฐฉ๋ฒ•๋“ค๋„ ๋™์  ์Šคํƒ€์ผ๋ง์ด ์–ด๋ ค์šด๊ฑด ์•„๋‹ˆ์ง€๋งŒ, ๋”ฐ์ง€์ž๋ฉด ์ด๊ฒŒ ์ข€ ๋” ํŽธํ•˜๋”๋ผ๊ตฌ์š”

๊ทธ๋ฆฌ๊ณ  CSS์ฝ”๋“œ๊ฐ€ JS์ฝ”๋“œ ์˜†์— ์กด์žฌํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ๋„ ์€๊ทผํžˆ ์žฅ์ ์ž…๋‹ˆ๋‹ค (colocation.)
๋”ฑ ํ•œ ํŒŒ์ผ์— ํ•„์š”ํ•œ CSS-in-JS ์ฝ”๋“œ๋งŒ์ด ํ•ด๋‹น ํŒŒ์ผ ์˜† ๋˜๋Š” ์‹ฌ์ง€์–ด ๊ฐ™์€ ํŒŒ์ผ์— ์กด์žฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค

ํ•œ ๊ฐ€์ง€ ์•„์‰ฌ์šด ์ ์€, ๊ฐœ๋ฐœ์ž๋„๊ตฌ์—์„œ ๊ฒ€์‚ฌํ•  ๋•Œ class์ด๋ฆ„์ด ๊ทธ๋ƒฅ ์ž„์˜์˜ ๋ฌธ์ž์—ด์ž…๋‹ˆ๋‹ค
์ด๊ฒŒ ์ง„์งœ ์•Œ์•„๋ณด๊ธฐ ํž˜๋“ค์–ด์ ธ์š”.
๊ทผ๋ฐ ์ด ๋‹จ์ ์€ ์ด๋Ÿฐ ํ”Œ๋Ÿฌ๊ทธ์ธ๊ฐ™์€ ๊ฒƒ๋“ค๋กœ ์ปค๋ฒ„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค

์ง„์งœ ๋‹จ์ ์œผ๋กœ๋Š” ๋Ÿฐํƒ€์ž„ ์„ฑ๋Šฅ์ด ์•ˆ์ข‹๋‹ค๋Š” ์ฐ์ด ์žˆ๋Š”๋ฐ.. ๋ฐ‘์—์„œ ์•Œ์•„๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

Material UI (UI ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ)

์‚ฌ์‹ค ๋Ÿฐํ…Œ์ผ์ด๋ผ๋Š” ์„œ๋น„์Šค๋ฅผ ๊ฐœ๋ฐœํ•˜๋Š” ๋‹จ๊ณ„์—์„œ ์ฒ˜์Œ์— ์ฃผ๋กœ Material UI๋ฅผ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค
Material UI๋ผ๋Š”๊ฑด ์ผ์ข…์˜.. UI๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋˜๋Š” ์ปดํฌ๋„ŒํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ˆ์š”
npm install @mui/material @emotion/react @emotion/styled ์ด๋ ‡๊ฒŒ ์ผ๋‹จ ์„ค์น˜ํ•˜๋ฉด
์ด ํŒจํ‚ค์ง€์—์„œ ์—ฌ๋Ÿฌ ๋ฏธ๋ฆฌ ์Šคํƒ€์ผ์ด ์ ์šฉ๋œ ์ปดํฌ๋„ŒํŠธ๋“ค์„ ์ œ๊ณตํ•ด์ค๋‹ˆ๋‹ค.
Material UI ์ด์™ธ์—๋„ Chakra UI, bootstrap, ... ์ด๋Ÿฐ๊ฒŒ ์žˆ๋‚˜๋ด…๋‹ˆ๋‹ค.

import React from "react";
import Button from "@mui/material/Button";

function App() {
  return (
    <Box sx={{ textAlign: "center", marginTop: "50px" }}>
      <Button variant="contained" color="primary">
        Click Me
      </Button>
      <Button variant="outlined" color="secondary" sx={{ marginLeft: "10px" }}>
        Cancel
      </Button>
    </Box>
  );
}

export default App;

๋™์  ์Šคํƒ€์ผ๋ง์€ mui๊ฐ™์€ ๊ฒฝ์šฐ sx props๋ฅผ inline-style์ฒ˜๋Ÿผ ๋ช…์‹œํ•˜๊ฒŒ ํ•˜๊ฑฐ๋‚˜, ์•„์˜ˆ ๋ช‡๋ช‡ ์ž์ฃผ ์“ฐ์ด๋Š” ํ† ํฐ์„ props๋กœ ๋„ฃ์„ ์ˆ˜ ์žˆ์œผ๋‹ˆ ์ด๊ฑธ ์ด์šฉํ•ฉ๋‹ˆ๋‹ค.

<Button variant="contained" color={isCancel ? "red" : "blue"}>
  This is button
</Button>

์žฅ๋‹จ์ 

Material UI ์žฅ๋‹จ์ 

์žฅ์ ์œผ๋กœ๋Š”, ์ผ๋‹จ "์šฐ๋ฆฌ ํ”„๋กœ์ ํŠธ ๊น”์—๋Š” ์ด๊ฒŒ ๋งž๊ฒ ๋‹ค" ์‹ถ์€๊ฑธ ์ž˜ ๊ณ ๋ฅด๊ณ  ์‹œ์ž‘ํ•œ๋‹ค๋ฉด, ๋ณ„๋„์˜ CSS์ฝ”๋“œ๋ฅผ ๊ฑฐ์˜ ์ž‘์„ฑํ•˜์ง€ ์•Š์•„๋„ ๋ฉ๋‹ˆ๋‹ค
๋Ÿฐํ…Œ์ผ ํ”„๋กœ์ ํŠธ๋„ ์ฒ˜์Œ์—๋Š” ์ƒ‰์ƒ ์Šคํƒ€์ผ ๊ตฌ์ƒํ–ˆ๋˜๊ฒŒ ๋”ฑ MUI ๊น”์ด๋ผ์„œ, ์ด๊ฑธ ๊ฑฐ์˜ ๊ฐ€์ ธ๋‹ค ์ผ์–ด์š”
๊ทธ๋žฌ๋”๋‹ˆ ์•„์˜ˆ ์ „์ฒด ํ”„๋กœ์ ํŠธ์—์„œ css์ฝ”๋“œ๋Š” ๊ฑฐ์˜ ์—†์—ˆ์Šต๋‹ˆ๋‹ค
๊ทผ๋ฐ ๋””์ž์ธ ์ปจ์…‰์„ ์ƒˆ๋กœ ์ˆ˜์ •ํ•˜๋Š” v2์ž‘์—…์„ ์ง„ํ–‰ํ•˜๋ฉด์„œ ๊ฑฐ์˜ ์“ธ๋ชจ๊ฐ€ ์—†์–ด์กŒ์ง€๋งŒ..

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

๊ทธ๋ฆฌ๊ณ  ๊ทธ UI๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ๋Œ€ํ•ด ์กฐ๊ธˆ์€ ์•Œ๊ณ  ์‹œ์ž‘ํ•ด์•ผํ•˜๋Š” ๋Ÿฌ๋‹์ปค๋ธŒ๊ฐ€ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค
MUI๋ฅผ ์˜ˆ๋กœ ๋“ค๋ฉด, <Box>๋ฅผ ์ด์šฉํ•ด์•ผ๊ฒ ๊ตฌ๋‚˜, <Card>๋ฅผ ์‚ฌ์šฉํ•ด์•ผ์ง€, ์ด๋Ÿฐ๊ฑฐ๋ฅผ ์ฒ˜์Œ๋ถ€ํ„ฐ ์•Œ ์ˆ˜๋Š” ์—†์œผ๋‹ˆ๊นŒ์š”

๊ทธ๋ฆฌ๊ณ  MUI๋Š” <Grid>, <TreeView>๊ฐ™์€ ์œ ํ‹ธ๋ฆฌํ‹ฐ UI ์ปดํฌ๋„ŒํŠธ ์…‹๋„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
์ด๊ฑด ๊ฝค ๊ฐ•๋ ฅํ•œ๋ฐ, <Grid>๋กœ ๋ฐ˜์‘ํ˜• ๋ ˆ์ด์•„์›ƒ์„ ๋นˆํ‹ˆ์—†์ด ๊ตฌ์„ฑํ•  ์ˆ˜๋„ ์žˆ๊ณ 
<TreeView>๊ฐ™์€ ๊ฒฝ์šฐ, ๋ณต์žกํ•œ UI๊ตฌ์„ฑ์„ ๋นŒํŠธ์ธ ์ปดํฌ๋„ŒํŠธ๋กœ ํŽธํ•˜๊ฒŒ ์ž‘์„ฑํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค

Tailwind CSS

tailwind css๋Š” utility-first CSS ํ”„๋ ˆ์ž„์›Œํฌ์ž…๋‹ˆ๋‹ค.

Tailwind CSSย is a utility-first CSS framework for rapidly building modern websites without ever leaving your HTML.

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

<button className="bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-700">
  Click Me
</button>

์ด๋Ÿฐ ์‹์œผ๋กœ, ๊ฐ„๋‹จํžˆ ์œ ํ‹ธ๋ฆฌํ‹ฐ ํด๋ž˜์Šค๋ฅผ ๋ช‡ ๊ฐœ ๋•์ง€๋•์ง€ ๋ถ™์—ฌ์„œ ์Šคํƒ€์ผ์„ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.
์˜ˆ๋ฅผ ๋“ค์–ด, "๊ธ€์”จ๋Š” ํฐ์ƒ‰์ด๋ฉด ์ข‹๊ฒ ์–ด" => text-white

Tailwind CSS๋ฅผ ์‚ฌ์šฉํ•˜๋Š”๋ฐ ์กฐ๊ฑด๋ถ€๋กœ ์Šคํƒ€์ผ์„ ์ ์šฉํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด
๊ฐ„๋‹จํžˆ, ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ์˜ ํ…œํ”Œ๋ฆฟ ๋ฆฌํ„ฐ๋Ÿด ๋ฌธ๋ฒ•์œผ๋กœ ์›ํ•˜๋Š” ์ž๋ฆฌ์— ์‰ฝ๊ฒŒ ๋™์ ์œผ๋กœ ์œ ํ‹ธ๋ฆฌํ‹ฐ ํด๋ž˜์Šค๋ฅผ ๋„ฃ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค

<button
  className={`bg-blue-500 ${theme === "DARK" ? "text-white" : "text-black"}`}
>
  Click Me
</button>

์ด๋ ‡๊ฒŒ์š”

์•„๋‹ˆ๋ฉด ๋ฌธ์ž์—ด ๋’ค์— + " text-white"์ฒ˜๋Ÿผ ๋ง๋ถ™์—ฌ์„œ ์Šคํƒ€์ผ์„ ๋นŒ๋“œํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค
๋ฌผ๋ก  ์ค‘๊ฐ„์ค‘๊ฐ„์— ๊ณต๋ฐฑ์ด ์ž˜ ๋“ค์–ด๊ฐ€๊ฒŒ ํ•ด์ฃผ๋„๋ก ํ•ฉ์‹œ๋‹ค
์ด๋Ÿฐ ์‹์œผ๋กœ ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๋œป

const buttonClassName = "rounded ";
if (selectedColor === "RED") buttonClassName += "bg-red-500 ";
if (selectedColor === "GREEN") buttonClassName += "bg-green-500 ";
if (selectedColor === "BLUE") buttonClassName += "bg-blue-500 ";
...
return <button className={buttonClassName}>Click Me</button>

๊ทธ๋Ÿผ ๋ณต์žกํ•œ ์กฐ๊ฑด๋ฌธ์ด๋ผ๋„ ๋ฌธ์ œ์—†์ด ์ฃผ๋ฅด๋ฅต ๋‹ค ๊ฑฐ์ณ์„œ ๋ฌธ์ž์—ด๋กœ ์‰ฝ๊ฒŒ ๋นŒ๋“œํ•˜๊ณ  ๊ฝ‚์•„๋„ฃ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค

์žฅ๋‹จ์ 

Tailwind CSS ์žฅ๋‹จ์ 

๋‹จ์ ์œผ๋กœ๋Š”..
๋งค์šฐ ๊ธด.. ๋”์ฐํ•˜๊ฒŒ ๊ธด ํด๋ž˜์Šค์ด๋ฆ„์„ ๊ฐ–๋Š” ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋“ฑ์žฅํ•ด๋ฒ„๋ฆฝ๋‹ˆ๋‹ค ;; ๋ง‰ ์“ฐ๋‹ค๋ณด๋ฉด ์ƒ๊ฐ๋ณด๋‹ค ๊ธธ์–ด์ ธ์š”.
๊ทธ๋ฆฌ๊ณ  ๋ฌผ๋ก  ๋ชจ๋˜ ๋””์ž์ธ์„ ์œ„ํ•ด ์—„์„ ๋œ ์œ ํ‹ธ๋ฆฌํ‹ฐ ํด๋ž˜์Šค๋“ค์ด์ง€๋งŒ, ๊ทธ๋งŒํผ ์ž์œ ๋„๊ฐ€ ๋–จ์–ด์ง‘๋‹ˆ๋‹ค

์žฅ์ ์œผ๋กœ๋Š”.. ๊ทธ๋ž˜๋„ ์•‰์€ ์ž๋ฆฌ์—์„œ ์Šคํƒ€์ผ์„ ๋ชจ๋‘ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๊ณ , ์—„์„ ๋œ ๋ชจ๋˜ ์Šคํƒ€์ผ์šฉ ์œ ํ‹ธ๋ฆฌํ‹ฐ ํด๋ž˜์Šค๋“ค์ด๋ผ ์€๊ทผ ํŽธํ•œ๊ฑด ๋งž์Šต๋‹ˆ๋‹ค.
์œ ํ‹ธ๋ฆฌํ‹ฐ ํด๋ž˜์Šค๋“ค์„ ์ข€ ์“ฐ๋‹ค๋ณด๋ฉด ์€๊ทผํžˆ ์ต์ˆ™ํ•ด์ ธ์„œ ๊ฝค ๋น ๋ฅด๊ฒŒ ์Šคํƒ€์ผ์ž‘์—…์„ ์ง„ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค
์ง„์งœ ์ด๊ฒƒ๋งŒ ์“ฐ๋ฉด์„œ ํ•œ ๋ช‡ ์ฃผ ์ง€๋‚˜๋ฉด? ๋˜๋Š” ๋ฉฐ์น ๋‚ด๋กœ? ์™„์ „ํžˆ ์ƒ์‚ฐ์„ฑ์ด ์—„์ฒญ๋‚˜์งˆ ๊ฒƒ ๊ฐ™๋‹ค๋Š” ๋Š๋‚Œ์ด ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค

๊ทผ๋ฐ ์ด๊ฑด ๊ณง ๋‹จ์ ์ด ๋  ์ˆ˜ ์žˆ์–ด์š”
์ƒˆ๋กญ๊ฒŒ ๋ฐฐ์›Œ์•ผ ํ• ๊ฒŒ ์ƒ๊ธด๋‹ค๋Š” ๋œป์ด๊ณ , ์ถ”๊ฐ€์ ์ธ ๋Ÿฌ๋‹ ์ปค๋ธŒ๊ฐ€ ์กด์žฌํ•˜๋Š” ์…ˆ์ž…๋‹ˆ๋‹ค
CSS๋งŒ ์•Œ๋ฉด ๋์ง€ ๋˜ ๊ฐ๊ฐ์˜ ์œ ํ‹ธ๋ฆฌํ‹ฐ ํด๋ž˜์Šค๋“ค์„ ์™ธ์šฐ๋ ค๋ฉด ์‹ฑ์ฐŒ์ฆ๋‚  ์ˆ˜ ์žˆ์–ด์š”

์ €๋Š” ์ปดํฌ๋„ŒํŠธ์˜ ์žฌ์‚ฌ์šฉ์„ฑ์ด ๋–จ์–ด์ง„๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ๋Š”๋ฐ, ์‚ฌ์‹ค ๊ทธ๋ ‡์ง€๋Š” ์•Š์€ ๊ฒƒ ๊ฐ™์•„์š”. ์˜คํžˆ๋ ค ๋” ์šฉ์ดํ•  ์ˆ˜๋„ ์žˆ๊ฒ ๋‹ค๋Š” ์ƒ๊ฐ์„ ํ•ฉ๋‹ˆ๋‹ค
๋ฏธ๋ฆฌ ๊ณตํ†ต๋ฒ„ํŠผ๊ฐ™์€ ์žฌ์‚ฌ์šฉ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋“ค์–ด๋‘๊ณ , ์ด๊ฑธ ๋ถ„๋ฆฌํ•ด๋‘” ๋‹ค์Œ์—, ๋‚˜์ค‘์— ๊ฐ–๋‹ค ์“ฐ๊ณ  ํ•„์š”์— ๋”ฐ๋ผ ํด๋ž˜์Šค์ด๋ฆ„์„ ๋˜ ๊ฑฐ๊ธฐ๋‹ค ๋ง๋ถ™์—ฌ์„œ ์Šคํƒ€์ผ์„ ์ปค์Šคํ…€ํ•  ์ˆ˜๋„ ์žˆ๊ฒ ์Šต๋‹ˆ๋‹ค

CSS-in-JS ์„ฑ๋Šฅ์— ๋Œ€ํ•œ ๊ดด์†Œ๋ฌธ์€ ์ง„์งœ์ธ๊ฐ€์š”?

CSS-in-JS๋Š” ํŽธํ•˜์ง€๋งŒ ์ตœ์ข…์ ์œผ๋กœ๋Š” runtime์— ์ด๋ฅผ ๋ณ€ํ™˜ํ•ด์ฃผ๋Š” ์ž‘์—…์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค
์ด๋Š” ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•๋“ค๊ณผ๋Š” ๋‹ค๋ฅด๊ฒŒ ์„ฑ๋Šฅ ์˜ค๋ฒ„ํ—ค๋“œ๋ฅผ ์œ ๋ฐœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค
๋ผ๋Š” ์ด์•ผ๊ธฐ๊ฐ€ ์žˆ์–ด์„œ, ํ•œ๋ฒˆ ๊ฐ„๋‹จํžˆ ํ™•์ธํ•ด๋ดค์Šต๋‹ˆ๋‹ค.

๊ฐ„๋‹จํ•œ ์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•œ ์„ธํŒ…

๊ฐ„๋‹จํ•œ ์ฝ”๋“œ๋“ค์„ ์ž‘์„ฑํ• ๊ฑด๋ฐ, ๋ณ„ ์‹ ๊ฒฝ์“ฐ์ง€ ์•Š๊ณ  ๊ฒฐ๊ณผ๋งŒ ๋ณด์…”๋„ ๋ฉ๋‹ˆ๋‹ค

import { useState } from "react";

export default function VanilaTest() {
  const [isActive, setIsActive] = useState(false);

  return (
    <div>
      <button onClick={() => setIsActive(prev => !prev)}>
        Toggle Active State
      </button>
      <TestArea isActive={isActive} />
    </div>
  );
}

function TestArea({ isActive }) {
  return (
    <div className={`test-element ${isActive ? "active" : "inactive"}`}>
      Vanila CSS Test
      {isActive && <div className="status-badge">Active!!</div>}
    </div>
  );
}
.test-element {
  width: 300px;
  height: 200px;
  background-color: lightgray;
  border-radius: 10px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  font-size: 20px;
  font-weight: bold;
  color: #333;
  transition: all 0.3s ease;
}

.test-element.active {
  background-color: lightcoral;
  color: white;
}

.test-element.inactive {
  background-color: lightblue;
  color: darkblue;
}

.status-badge {
  margin-top: 10px;
  padding: 5px 10px;
  background-color: orange;
  color: white;
  border-radius: 5px;
  font-size: 14px;
}

๊ทธ๋ƒฅ vanila CSS๋ฅผ ์‚ฌ์šฉํ•˜๋Š” VanilaTest ์ปดํฌ๋„ŒํŠธ๋ฅผ ์œ„์™€ ๊ฐ™์ด ์ž‘์„ฑํ•˜๊ณ 

import { useState } from "react";
import styled from "@emotion/styled";

export default function EmotionTest() {
  const [isActive, setIsActive] = useState(false);

  return (
    <div>
      <button onClick={() => setIsActive(prev => !prev)}>
        Toggle Active State
      </button>
      <TestElement isActive={isActive}>
        Emotion Test
        {isActive && <StatusBadge>Active!!</StatusBadge>}
      </TestElement>
    </div>
  );
}

const TestElement = styled.div`
  width: 300px;
  height: 200px;
  background-color: ${props => (props.isActive ? "lightcoral" : "lightblue")};
  border-radius: 10px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  font-size: 20px;
  font-weight: bold;
  color: ${props => (props.isActive ? "white" : "darkblue")};
  transition: all 0.3s ease;
`;

const StatusBadge = styled.div`
  margin-top: 10px;
  padding: 5px 10px;
  background-color: orange;
  color: white;
  border-radius: 5px;
  font-size: 14px;
`;

CSS-in-JS ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ธ emotion์„ ์‚ฌ์šฉํ•˜๋Š” EmotionTest ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋“ค์—ˆ์–ด์š”
๋น„๊ต์— ๋„์›€์ด ๋ ๊นŒ ์‹ถ์–ด ์ผ๋ถ€๋Ÿฌ ์Šคํƒ€์ผ ์ค„๋„ ๋งŽ์ด ๋„ฃ๊ณ , ์กฐ๊ฑด๋ถ€ ์Šคํƒ€์ผ๋„ ์ถ”๊ฐ€ํ•ด๋ฒ„๋ ธ์Šต๋‹ˆ๋‹ค.
์ด์ •๋„๋ฉด ์กฐ๊ฑด์€ ๋˜‘๊ฐ™๊ฒ ์ฃ ?

์•„, Tailwind๋„ ๊ฐ™์ด ํ•ด๋ณด๊ณ ์‹ถ์–ด์ ธ์„œ TailwindTest๋„ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค

export default function TailwindTest() {
  const [isActive, setIsActive] = useState(false);

  return (
    <div>
      <button onClick={() => setIsActive(prev => !prev)}>
        Toggle Active State
      </button>
      <TestArea isActive={isActive} />
    </div>
  );
}

function TestArea({ isActive }) {
  return (
    <div
      className={`flex flex-col items-center w-[300px] h-[200px] rounded-lg shadow-lg justify-center text-xl font-bold transition-all duration-300 ${
        isActive
          ? "bg-[lightcoral] text-white"
          : "bg-[lightblue] text-[darkblue]"
      }`}
    >
      Tailwind Test
      {isActive && (
        <div className="mt-2 px-4 py-2 bg-[orange] text-white rounded-lg text-sm">
          Active!!
        </div>
      )}
    </div>
  );
}

์ฒ˜์Œ์—๋Š” ๋”ฐ๋กœ TestArea๋ฅผ ๋ถ„๋ฆฌํ•˜์ง€ ์•Š์•˜๋Š”๋ฐ,
๋‚˜์ค‘์— ์ƒ๊ฐํ•ด๋ณด๋‹ˆ Emotion์€ ์ปดํฌ๋„ŒํŠธ ํ•˜๋‚˜ ๋” ๋บ€ ์…ˆ์ด๋‹ˆ๊นŒ Emotion๊ฐ€ ๋” ๋ถˆ๋ฆฌํ•˜๋ ค๋‚˜? ์‹ถ์–ด์„œ,
์Šคํƒ€์ผ์ด ์ ์šฉ๋œ ์˜์—ญ์„ Vanila์™€ Tailwind์—์„œ๋„ ๋ณ„๋„์˜ ์ปดํฌ๋„ŒํŠธ๋กœ ๋ถ„๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค
์ด์ œ ์ด ์ปดํฌ๋„ŒํŠธ๋“ค์„ ์ ๋‹นํžˆ ์ž˜ ๋‚˜์—ดํ–ˆ์Šต๋‹ˆ๋‹ค

function App() {
  return (
    <>
      <VanilaTest />
      <br />
      <EmotionTest />
      <br />
      <TailwindTest />
    </>
  );
}

์ปดํฌ๋„ŒํŠธ ๊ตฌ์„ฑ

์„ฑ๋Šฅ ํ…Œ์ŠคํŠธํ•ด๋ณด๊ธฐ

์ด์ œ React Dev Tools - Profiler๋ฅผ ์ผœ๊ณ , ์ฒซ ๋ Œ๋”๋ง์˜ FlameGraph๋ฅผ ์‚ดํŽด๋ด…์‹œ๋‹ค

์ฒซ ๋ Œ๋”๋ง ์„ฑ๋Šฅ

VanilaTest, EmotionTest, TailwindTest ๊ฐ๊ฐ์ด ์‚ฌ์ด๋†“๊ฒŒ ์–ด๊นจ๋™๋ฌดํ•˜๊ณ  ์žˆ๋„ค์š”
๊ทผ๋ฐ ๋”ฑ๋ด๋„ Emotion์˜ ์–ด๊นจ๊ฐ€ ๋„ˆ๋ฌด ๋„“์Šต๋‹ˆ๋‹ค
์—„์ฒญ๋‚œ ์ฐจ์ด๋ฅผ ๋ณด์—ฌ์ฃผ๋„ค์š”..
๋„ˆ๋ฌด ๊ฐ„๋‹จํ•œ ์„ฑ๋Šฅํ…Œ์ŠคํŠธ๋ผ์„œ ์ •ํ™•ํžˆ ์–ด๋Š์ •๋„ ์ˆ˜์น˜์˜ ์ฐจ์ด๊ฐ€ ๋‚œ๋‹ค, ๋ฅผ ๊ฒฐ๋ก ์ง“๊ธฐ๋Š” ์กฐ์‹ฌ์Šค๋Ÿฝ์Šต๋‹ˆ๋‹ค๋งŒ
div (Styled)๊ฐ€ ํ™•์‹คํžˆ ๋ Œ๋”๋ง ์„ฑ๋Šฅ์ด ์•ˆ ์ข‹์€ ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค

๋ฒ„ํŠผ์„ ํ† ๊ธ€ํ•ด์„œ ๋ฆฌ๋ Œ๋”๋ง ์„ฑ๋Šฅ ํ”„๋กœํŒŒ์ผ๋ง๋„ ํ•œ๋ฒˆ ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค
<Profiler>๋กœ ์„ธ ๊ฐœ์˜ ํ…Œ์ŠคํŠธ ์ปดํฌ๋„ŒํŠธ๋“ค์„ ๊ฐ์‹ธ์„œ ํ”„๋กœํŒŒ์ผ๋ง ๋กœ๊ทธ๋ฅผ ๋ชจ์„ ์ˆ˜ ์žˆ๋Š”๋ฐ
์•„๋ž˜์™€ ๊ฐ™์ด ์ฝ”๋“œ๋ฅผ ์งœ์คฌ์Šต๋‹ˆ๋‹ค.

// ์„ฑ๋Šฅ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•  ๊ฐ์ฒด (๊ฐ id ๋ณ„๋กœ ์„ฑ๋Šฅ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅ)
const performanceData = {};

// onRender ์ฝœ๋ฐฑ ํ•จ์ˆ˜
function onRenderCallback(
  id,
  phase,
  actualDuration,
  baseDuration,
  startTime,
  commitTime
) {
  // ํ•ด๋‹น id์— ๋Œ€ํ•œ ์„ฑ๋Šฅ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์œผ๋ฉด ์ดˆ๊ธฐํ™”
  if (!performanceData[id]) {
    performanceData[id] = {
      count: 0,
      totalActualDuration: 0,
      minActualDuration: Infinity,
      maxActualDuration: 0
    };
  }

  // ํ•ด๋‹น id์— ๋Œ€ํ•œ ์„ฑ๋Šฅ ๋ฐ์ดํ„ฐ๋ฅผ ์—…๋ฐ์ดํŠธ
  const data = performanceData[id];
  data.count += 1;
  data.totalActualDuration += actualDuration;
  data.minActualDuration = Math.min(data.minActualDuration, actualDuration);
  data.maxActualDuration = Math.max(data.maxActualDuration, actualDuration);

  // ํ‰๊ท  ๋ Œ๋”๋ง ์‹œ๊ฐ„ ๊ณ„์‚ฐ
  const averageActualDuration = data.totalActualDuration / data.count;

  const result =
    `Profiler Data for ${id} (${phase} phase): \n` +
    `Actual duration: ${actualDuration.toFixed(2)}ms \n` +
    `Base duration: ${baseDuration.toFixed(2)}ms \n` +
    `Total renders: ${data.count} \n` +
    `Average actual duration: ${averageActualDuration.toFixed(2)}ms \n` +
    `Min actual duration: ${data.minActualDuration.toFixed(2)}ms \n` +
    `Max actual duration: ${data.maxActualDuration.toFixed(2)}ms \n`;

  console.log(result);
}

function App() {
  return (
    <>
      <Profiler id="vanila" onRender={onRenderCallback}>
        <VanilaTest />
      </Profiler>
      <br />
      <Profiler id="emotion" onRender={onRenderCallback}>
        <EmotionTest />
      </Profiler>
      <br />
      <Profiler id="tailwind" onRender={onRenderCallback}>
        <TailwindTest />
      </Profiler>
    </>
  );
}

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด onRender props๋กœ ๋“ฑ๋กํ•œ onRenderCallbackํ•จ์ˆ˜๋Š” ๋งค๋ฒˆ ๋ Œ๋”๋ง์ด ์ผ์–ด๋‚  ๋•Œ๋งˆ๋‹ค ํ˜ธ์ถœ๋˜๋ฉฐ
๊ฐ ์ปดํฌ๋„ŒํŠธ์˜ id์— ๋”ฐ๋ผ ๊ฐ๊ธฐ ๋”ฐ๋กœ ์ €์žฅ๋˜๊ณ  ์œ ์ง€๋ฉ๋‹ˆ๋‹ค
์ด์ œ ํ† ๊ธ€๋ฒ„ํŠผ์„ ๊ฐ๊ฐ 50๋ฒˆ์”ฉ ๋ˆ„๋ฅด๊ณ  ์ฝ˜์†”๋กœ๊ทธ๋ฅผ ๋ด…์‹œ๋‹ค

๋ฆฌ๋ Œ๋”๋ง ํ†ต๊ณ„๋‚ธ ๊ฒฐ๊ณผ

๋ Œ๋”๋ง์— ํ‰๊ท ์ ์œผ๋กœ ์†Œ์š”๋˜๋Š” ์‹œ๊ฐ„(Average actual duration)์€..

  1. vanila CSS : 0.35ms
  2. Emotion : 0.48ms
  3. tailwind css : 0.27ms

Tailwind > Vanila CSS > Emotion ์ˆœ์œผ๋กœ ๋นจ๋ž๋„ค์š”
์ƒ๊ฐ๋ณด๋‹ค Tailwind๊ฐ€ ์ข€ ๋น ๋ฆ…๋‹ˆ๋‹ค..
๊ทธ๋ฆฌ๊ณ  ์ƒ๊ฐ๋ณด๋‹ค vanila CSS๊ฐ€ ์กฐ๊ธˆ ๋Š๋ฆด ๋•Œ๊ฐ€ ์žˆ๋„ค์š”?
์žฌ๋ฐŒ๋„ค์š”.. ์—ฐ๊ตฌ๋ฅผ ์ข€ ๋” ํ•˜๋ฉด ์žฌ๋ฐŒ์„ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค

์•„๋ฌดํŠผ ๊ฐ„๋‹จํ•œ ํ™˜๊ฒฝ์—์„œ ๊ฐ ๋ฐฉ๋ฒ•๋“ค์˜ ์„ฑ๋Šฅ์„ ๋น„๊ตํ•˜๊ณ 
CSS-in-JS๊ฐ€ ์„ฑ๋Šฅ์ด ์•ˆ ์ข‹๋‹ค๋Š” ๊ดด์†Œ๋ฌธ์ด ์ง„์งœ์ธ์ง€ ํ™•์ธํ•ด๋ดค์Šต๋‹ˆ๋‹ค
๊ฝค๋‚˜ ์ง„์งœ์ธ ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ด๋„ค์š”. ํ™•์‹คํžˆ ๋Ÿฐํƒ€์ž„ ๋ณ€ํ™˜์ด ํ•„์š”ํ•˜๋‹ˆ๊นŒ ๊ทธ๋Ÿด๋งŒ ํ•˜๊ณ ์š”
๊ฐ„๋‹จํ•œ ์˜ˆ์ œ๋กœ ํ™•์ธํ–ˆ๋Š”๋ฐ, ํ”„๋กœ์ ํŠธ๊ฐ€ ์ปค์งˆ์ˆ˜๋ก ํฐ ์ด์Šˆ๋กœ ๋‹ค๊ฐ€์˜ฌ ์ˆ˜๋„ ์žˆ๊ฒ ์Šต๋‹ˆ๋‹ค

๊ทธ๋ฆฌ๊ณ  ํŠน์ดํ•œ ๊ฒƒ์€, Emotion ์ผ€์ด์Šค์— ๋Œ€ํ•œ ํ”„๋กœํŒŒ์ผ๋ง ๊ฒฐ๊ณผ์—์„œ

insertion?

Insertion์ด๋ผ๋Š”.. ์ œ๊ฐ€ ์ถ”๊ฐ€ํ•˜์ง€๋„ ์•Š์€ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค
์Šคํƒ์˜ค๋ฒ„ํ”Œ๋กœ์šฐ๋ฅผ ์‚ด์ง ์ฐพ์•„๋ดค๋Š”๋ฐ, emotion์˜ ์Šคํƒ€์ผ๋ง ์—”ํŠธ๋ฆฌํฌ์ธํŠธ์— ํ•ด๋‹นํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ๋ผ๊ณ  ํ•ด์š”
์–ธ์  ๊ฐ€ ์ด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๊ฐ„๋‹จํžˆ ํ•œ๋ฒˆ ๋œฏ์–ด๋ณด๋Š”๊ฒƒ๋„ ์žฌ๋ฐŒ์„ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

๊ฒฐ๋ก ?

๊ทธ๋ž˜์„œ ๋ญ ์”€?

AI์นœ๊ตฌ์—๊ฒŒ ์š”์•ฝํ•ด๋‹ฌ๋ผ๊ณ  ํ•ด๋ดค์Šต๋‹ˆ๋‹ค.

๊ฒฐ๋ก ์€..

ํ 

์ €๋ผ๋ฉด

๋‹ค๋ฅธ ๋ณ„๋„์˜ ์˜์กด์„ฑ ์—†์ด ๊ฐ€๋ณ๊ฒŒ ๊ฐ€๊ณ ์‹ถ๋‹ค๋ฉด CSS Modules๋ฅผ ์„ ํƒํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. CSS์˜ ๊ณ ์งˆ์ ์ธ ๋ฌธ์ œ๋ฅผ ์ถฉ๋ถ„ํžˆ ํ•ด๊ฒฐํ•ด์ค„ ์ˆ˜ ์žˆ์–ด์š”

Emotion๊ฐ™์€ CSS-in-JS๋Š” ๋ณต์žกํ•œ ํ˜‘์—…์ด ์žˆ์„ ๋•Œ ์‚ฌ์šฉํ•˜๊ธฐ ์ข‹์€ ๊ฒƒ ๊ฐ™๋„ค์š”. ๋‹ค๋ฅธ์‚ฌ๋žŒ๋“ค ์ฝ”๋“œ๋ฅผ ์‹œ๋งจํ‹ฑํ•˜๊ฒŒ ์•Œ์•„๋ณผ ์ˆ˜ ์žˆ๋Š”๊ฒŒ ์žฅ์ ์ž…๋‹ˆ๋‹ค
ํŠนํžˆ Emotion์€ ์™€ ํ•จ๊ป˜ํ•  ๋•Œ ๋˜ํ•œ ๊ฐ•๋ ฅ(MUI ์˜คํ”ผ์…œ)ํ•ด์„œ, ๋‘˜์„ ๊ฐ™์ด ์‚ฌ์šฉํ•  ๋•Œ ์žฅ์ ์ด ๋งŽ์Šต๋‹ˆ๋‹ค.
๋Œ€์‹ ์— ์„ฑ๋Šฅ์„ ๊ทนํ•œ๊นŒ์ง€ ์•„๊ปด์•ผ ํ•˜๋Š” ํ”„๋กœ์ ํŠธ๋ผ๋ฉด ์žฌ๊ณ ํ•  ํ•„์š”๊ฐ€ ์žˆ๊ฒ ์Šต๋‹ˆ๋‹ค

๋งŒ์•ฝ ๋””์ž์ธ๊ฐ™์€๊ฑฐ์— ์‹ ๊ฒฝ์“ธ ๊ฒจ๋ฅผ์ด ์—†์ด ๋น ๋ฅธ ๊ฐœ๋ฐœ์ด ํ•„์š”ํ•˜๋ฉด, MUI๋‚˜ ๋‹ค๋ฅธ UI๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ข‹์„ ๋“ฏ ํ•ฉ๋‹ˆ๋‹ค.
๋งŽ์€ ๋นŒํŠธ์ธ ์ปดํฌ๋„ŒํŠธ๋กœ ๋น ๋ฅด๊ฒŒ ๊ฐ•๋ ฅํ•œ UI๋ฅผ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋„์™€์ค๋‹ˆ๋‹ค
์ ๋‹นํžˆ ํŒ€์›๋“ค๊ณผ ์ƒ์˜ํ•ด์„œ ํ”„๋กœ์ ํŠธ์— ์ ์ ˆํ•œ ํ…Œ๋งˆ๋งŒ์„ ๊ณ ๋ฅด๋ฉด ๋˜๊ฒ ์ฃ 
์ €๋„ ํ•ด์ปคํ†คํ•  ๋•Œ ๊ทธ๋ž˜์„œ MUI๋ฅผ ์„ ํƒํ–ˆ์Šต๋‹ˆ๋‹ค.

๋นจ๋ฆฌ๋นจ๋ฆฌ

Tailwind๋กœ๋„ ๋น ๋ฅด๊ฒŒ ๋””์ž์ธ์„ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์• ์ดˆ์— ๊ทธ๋Ÿฌ๋ ค๊ณ  ๋งŒ๋“ค์–ด์กŒ๊ณ ์š”
๊ทธ๋ฆฌ๊ณ  ์œ„์—์„œ ์‚ดํŽด๋ณธ ๊ฒƒ์ฒ˜๋Ÿผ, ์„ฑ๋Šฅ์— (vanila CSS๋ณด๋‹ค๋„??) ๊ฝค ํฐ ์ด์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค
๋ฒˆ๋“ค์‚ฌ์ด์ฆˆ๋Š” ์–ด๋–จ๊นŒ? ์‹ถ์–ด์„œ ์‚ด์ง ์ฐพ์•„๋ดค๋Š”๋ฐ, ๋นŒ๋“œ์‹œ์— Tailwind ๋‚ด๋ถ€์—์„œ ํ•„์š”์—†๋Š” CSS๋ผ์ธ๋“ค์„ ์ •๋ฆฌํ•ด์ฃผ๊ธฐ ๋•Œ๋ฌธ์— ๋ฒˆ๋“ค ์‚ฌ์ด์ฆˆ๋„ ๊ฒฝ๋Ÿ‰์ด๋ผ๊ณ  ํ•˜๋„ค์š”
๋งค์šฐ ๊นƒํ„ธ์ฒ˜๋Ÿผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ™์Šต๋‹ˆ๋‹ค
๋Œ€์‹  ํ˜‘์—…ํ•  ๋•Œ๋Š” ์•Œ์•„๋ณด๊ธฐ ํž˜๋“ค ์ˆ˜๋„ ์žˆ๊ฒ ๋„ค์š”.

์ด๋Ÿฌํ•œ ์ ๋“ค์„ ๊ณ ๋ คํ•ด์„œ ์ƒํ™ฉ์— ๋งž๊ฒŒ ์‚ฌ์šฉํ•˜์‹œ๋ฉด ์ข‹์„ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค
์ œ ๊ณ ๋ฏผ๋“ค์ด ์กฐ๊ธˆ์ด๋‚˜๋งˆ ๋„์›€์ด ๋˜์—ˆ์œผ๋ฉด ํ•˜๋„ค์š”



์•„ํœด.. ์›๋ž˜ ์ด๋ ‡๊ฒŒ๊นŒ์ง€ ๊ธธ๊ฒŒ ์“ธ ์ƒ๊ฐ์ด ์•„๋‹ˆ์—ˆ๋Š”๋ฐ
์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ ํ•œ๋ฒˆ ํ•ด๋ณด๋ ค๋‹ค๋ณด๋‹ˆ๊นŒ ์š•์‹ฌ์ด ๋‚˜์„œ ๊ฝค ๊ธธ๊ฒŒ ์ผ์Šต๋‹ˆ๋‹ค
๊ทธ๋ž˜๋„ ์žฌ๋ฐŒ์—ˆ๋„ค์š”
์ด๋งŒ ๋งˆ์นฉ๋‹ˆ๋‹ค


ยฉ 2024, Built with Gatsby