React CSS ๋ชจ๋ ์์ ๊ฐ์ด๋
๋ชฉ์ฐจ
CSS ๋ชจ๋์ด๋?
CSS ๋ชจ๋(CSS Modules)์ CSS ํด๋์ค๋ช
์ ๋ก์ปฌ ์ค์ฝํ๋ก ์ ํํ์ฌ ํด๋์ค๋ช
์ถฉ๋์ ๋ฐฉ์งํ๋ ๊ธฐ์ ์
๋๋ค. React์์ ์ปดํฌ๋ํธ๋ณ๋ก ๋
๋ฆฝ์ ์ธ ์คํ์ผ์ ์ ์ฉํ ๋ ๋งค์ฐ ์ ์ฉํฉ๋๋ค.
์ผ๋ฐ CSS vs CSS ๋ชจ๋
์ผ๋ฐ CSS์ ๋ฌธ์ ์ :
/* styles.css */
.button {
background-color: blue;
color: white;
}
CSS
๋ณต์ฌ
โ ์ ์ญ ์ค์ฝํ์์ .button ํด๋์ค๊ฐ ๋ชจ๋ ์ปดํฌ๋ํธ์ ์ํฅ์ ๋ฏธ์นจ
CSS ๋ชจ๋์ ํด๊ฒฐ์ฑ
:
/* Button.module.css */
.button {
background-color: blue;
color: white;
}
CSS
๋ณต์ฌ
โ ํด๋์ค๋ช
์ด Button_button__hash123 ํํ๋ก ๋ณํ๋์ด ์ถฉ๋ ๋ฐฉ์ง
CSS ๋ชจ๋์ ์ฅ์
1. ์ค์ฝํ ๊ฒฉ๋ฆฌ
โข
์ปดํฌ๋ํธ๋ณ๋ก ๋
๋ฆฝ์ ์ธ CSS ์ค์ฝํ ์ ๊ณต
โข
ํด๋์ค๋ช
์ถฉ๋ ์์ ๋ฐฉ์ง
2. ์ ์ง๋ณด์์ฑ ํฅ์
โข
์คํ์ผ๊ณผ ์ปดํฌ๋ํธ์ 1:1 ๋งคํ
โข
์ฝ๋ ๋ฆฌํฉํ ๋ง ์ ์์ ์ฑ ๋ณด์ฅ
3. ๋ช ์์ ์์กด์ฑ
โข
import ๋ฌธ์ ํตํ ๋ช
ํํ ์คํ์ผ ์์กด์ฑ ํํ
โข
์ฌ์ฉํ์ง ์๋ ์คํ์ผ ์ฝ๊ฒ ์๋ณ ๊ฐ๋ฅ
4. ๋น๋ ํ์ ์ต์ ํ
โข
์ฌ์ฉ๋์ง ์๋ CSS ์๋ ์ ๊ฑฐ
โข
ํด๋์ค๋ช
์ต์ ํ๋ก ๋ฒ๋ค ํฌ๊ธฐ ๊ฐ์
๊ธฐ๋ณธ ์ฌ์ฉ๋ฒ
1. CSS ๋ชจ๋ ํ์ผ ์์ฑ
ํ์ผ๋ช
๊ท์น: ComponentName.module.css
/* Button.module.css */
.primary {
background-color: #007bff;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
}
.secondary {
background-color: #6c757d;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
}
.large {
padding: 15px 30px;
font-size: 18px;
}
.small {
padding: 5px 10px;
font-size: 12px;
}
CSS
๋ณต์ฌ
2. React ์ปดํฌ๋ํธ์์ ์ฌ์ฉ
// Button.jsx
import React from 'react';
import styles from './Button.module.css';
const Button = ({ variant = 'primary', size = 'medium', children, ...props }) => {
return (
<button
className={`${styles[variant]} ${styles[size]}`}
{...props}
>
{children}
</button>
);
};
export default Button;
JavaScript
๋ณต์ฌ
3. ์ปดํฌ๋ํธ ์ฌ์ฉ ์์
// App.jsx
import React from 'react';
import Button from './components/Button';
function App() {
return (
<div>
<Button variant="primary" size="large">
Primary Large Button
</Button>
<Button variant="secondary" size="small">
Secondary Small Button
</Button>
</div>
);
}
export default App;
JavaScript
๋ณต์ฌ
๊ณ ๊ธ ์ฌ์ฉ๋ฒ
1. ๋์ ํด๋์ค๋ช
// Card.jsx
import styles from './Card.module.css';
const Card = ({ isActive, hasError, children }) => {
const cardClasses = [
styles.card,
isActive && styles.active,
hasError && styles.error
].filter(Boolean).join(' ');
return (
<div className={cardClasses}>
{children}
</div>
);
};
JavaScript
๋ณต์ฌ
2. classnames ๋ผ์ด๋ธ๋ฌ๋ฆฌ ํ์ฉ
๋จผ์ ์ค์น:
npm install classnames
Shell
๋ณต์ฌ
์ฌ์ฉ ์์ :
import classNames from 'classnames/bind';
import styles from './Card.module.css';
const cx = classNames.bind(styles);
const Card = ({ isActive, hasError, size }) => {
return (
<div className={cx('card', {
active: isActive,
error: hasError,
[`size-${size}`]: size
})}>
Content
</div>
);
};
JavaScript
๋ณต์ฌ
3. CSS ๋ณ์์ ํจ๊ป ์ฌ์ฉ
/* Theme.module.css */
.container {
--primary-color: #007bff;
--secondary-color: #6c757d;
--spacing: 1rem;
}
.button {
background-color: var(--primary-color);
margin: var(--spacing);
}
.darkTheme {
--primary-color: #0056b3;
--secondary-color: #545b62;
}
CSS
๋ณต์ฌ
import styles from './Theme.module.css';
const ThemedComponent = ({ isDark }) => {
return (
<div className={`${styles.container} ${isDark ? styles.darkTheme : ''}`}>
<button className={styles.button}>Themed Button</button>
</div>
);
};
JavaScript
๋ณต์ฌ
4. ์ค์ฒฉ ์ ํ์์ ์์ฌ ํด๋์ค
/* Navigation.module.css */
.nav {
display: flex;
list-style: none;
padding: 0;
}
.navItem {
margin-right: 1rem;
}
.navItem:hover {
background-color: #f8f9fa;
}
.navItem:last-child {
margin-right: 0;
}
.navLink {
text-decoration: none;
color: #333;
padding: 0.5rem 1rem;
border-radius: 4px;
transition: all 0.2s ease;
}
.navLink:hover {
background-color: #e9ecef;
}
.navLink.active {
background-color: #007bff;
color: white;
}
CSS
๋ณต์ฌ
์ค์ ์์
์์ ํ ์นด๋ ์ปดํฌ๋ํธ ๊ตฌํ
/* Card.module.css */
.card {
background: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
overflow: hidden;
transition: all 0.3s ease;
}
.card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
transform: translateY(-2px);
}
.header {
padding: 1.5rem;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.title {
margin: 0;
font-size: 1.25rem;
font-weight: 600;
}
.subtitle {
margin: 0.5rem 0 0 0;
opacity: 0.9;
font-size: 0.875rem;
}
.body {
padding: 1.5rem;
}
.footer {
padding: 1rem 1.5rem;
background: #f8f9fa;
border-top: 1px solid #e9ecef;
display: flex;
justify-content: space-between;
align-items: center;
}
.actions {
display: flex;
gap: 0.5rem;
}
.button {
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 0.875rem;
transition: all 0.2s ease;
}
.buttonPrimary {
background: #007bff;
color: white;
}
.buttonPrimary:hover {
background: #0056b3;
}
.buttonSecondary {
background: transparent;
color: #6c757d;
border: 1px solid #dee2e6;
}
.buttonSecondary:hover {
background: #e9ecef;
}
/* ๋ฐ์ํ ๋์์ธ */
@media (max-width: 768px) {
.card {
margin: 0.5rem;
}
.header,
.body,
.footer {
padding: 1rem;
}
.footer {
flex-direction: column;
gap: 1rem;
align-items: stretch;
}
.actions {
justify-content: center;
}
}
CSS
๋ณต์ฌ
// Card.jsx
import React from 'react';
import styles from './Card.module.css';
const Card = ({
title,
subtitle,
children,
onPrimaryAction,
onSecondaryAction,
primaryActionText = "ํ์ธ",
secondaryActionText = "์ทจ์"
}) => {
return (
<div className={styles.card}>
{(title || subtitle) && (
<div className={styles.header}>
{title && <h3 className={styles.title}>{title}</h3>}
{subtitle && <p className={styles.subtitle}>{subtitle}</p>}
</div>
)}
<div className={styles.body}>
{children}
</div>
{(onPrimaryAction || onSecondaryAction) && (
<div className={styles.footer}>
<div className={styles.actions}>
{onSecondaryAction && (
<button
className={`${styles.button} ${styles.buttonSecondary}`}
onClick={onSecondaryAction}
>
{secondaryActionText}
</button>
)}
{onPrimaryAction && (
<button
className={`${styles.button} ${styles.buttonPrimary}`}
onClick={onPrimaryAction}
>
{primaryActionText}
</button>
)}
</div>
</div>
)}
</div>
);
};
export default Card;
JavaScript
๋ณต์ฌ
์ฌ์ฉ ์์
// App.jsx
import React from 'react';
import Card from './components/Card';
function App() {
const handleSave = () => {
console.log('์ ์ฅ๋จ');
};
const handleCancel = () => {
console.log('์ทจ์๋จ');
};
return (
<div style={{ padding: '2rem', background: '#f5f5f5', minHeight: '100vh' }}>
<Card
title="์ฌ์ฉ์ ์ ๋ณด"
subtitle="ํ๋กํ ์ ๋ณด๋ฅผ ํ์ธํ๊ณ ์์ ํ์ธ์"
onPrimaryAction={handleSave}
onSecondaryAction={handleCancel}
primaryActionText="์ ์ฅ"
secondaryActionText="์ทจ์"
>
<p>์ด๋ฆ: ํ๊ธธ๋</p>
<p>์ด๋ฉ์ผ: hong@example.com</p>
<p>๊ฐ์
์ผ: 2024-01-15</p>
</Card>
</div>
);
}
export default App;
JavaScript
๋ณต์ฌ
ํธ๋ฌ๋ธ์ํ
1. ํด๋์ค๋ช ์ด ์ ์ฉ๋์ง ์๋ ๊ฒฝ์ฐ
๋ฌธ์ : ์คํ์ผ์ด ์ ์ฉ๋์ง ์์
// โ ์๋ชป๋ ๋ฐฉ๋ฒ
<div className="button">๋ฒํผ</div>
JavaScript
๋ณต์ฌ
ํด๊ฒฐ:
// โ
์ฌ๋ฐ๋ฅธ ๋ฐฉ๋ฒ
<div className={styles.button}>๋ฒํผ</div>
JavaScript
๋ณต์ฌ
2. ํ์ดํ์ด ํฌํจ๋ ํด๋์ค๋ช
CSS:
.nav-item {
color: blue;
}
CSS
๋ณต์ฌ
์ฌ์ฉ:
// โ ์๋ฌ ๋ฐ์
<div className={styles.nav-item}>
// โ
๋๊ดํธ ํ๊ธฐ๋ฒ ์ฌ์ฉ
<div className={styles['nav-item']}>
// โ
๋๋ ์นด๋ฉ์ผ์ด์ค๋ก ๋ณ๊ฒฝ
.navItem {
color: blue;
}
<div className={styles.navItem}>
JavaScript
๋ณต์ฌ
3. ์ ์ญ ์คํ์ผ๊ณผ ๋ชจ๋ ์คํ์ผ ์ถฉ๋
/* styles.module.css */
:global(.global-class) {
/* ์ ์ญ ์คํ์ผ */
}
.local-class {
/* ๋ก์ปฌ ์คํ์ผ */
}
/* ํน์ ์ ์ญ ํด๋์ค ๋ด๋ถ์ ๋ก์ปฌ ์คํ์ผ */
:global(.container) .localContent {
color: red;
}
CSS
๋ณต์ฌ
4. ๋น๋ ํ๊ฒฝ ๋ฌธ์
Create React App์ด ์๋ ๊ฒฝ์ฐ webpack ์ค์ :
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.module\.css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
modules: {
localIdentName: '[name]__[local]___[hash:base64:5]'
}
}
}
]
}
]
}
};
JavaScript
๋ณต์ฌ
๋์ ๋ฐ ๋น๊ต
1. CSS Modules vs Styled Components
ํน์ง | CSS Modules | Styled Components |
๋ฌ๋ ์ปค๋ธ | ๋ฎ์ | ์ค๊ฐ |
๋ฒ๋ค ํฌ๊ธฐ | ์์ | ํผ |
๋ฐํ์ ์ฑ๋ฅ | ์ข์ | ๋ณดํต |
๊ฐ๋ฐ์ ๋๊ตฌ | ๋ณดํต | ์ข์ |
ํ
๋ง ์ง์ | ์๋ | ๋ด์ฅ |
2. CSS Modules vs CSS-in-JS
// CSS Modules
import styles from './Button.module.css';
const Button = () => <button className={styles.primary}>๋ฒํผ</button>;
// CSS-in-JS (Emotion)
import { css } from '@emotion/react';
const Button = () => (
<button css={css`background: blue; color: white;`}>
๋ฒํผ
</button>
);
JavaScript
๋ณต์ฌ
3. ์ธ์ CSS Modules๋ฅผ ์ฌ์ฉํด์ผ ํ ๊น?
CSS Modules ๊ถ์ฅ ์ํฉ:
โข
๊ธฐ์กด CSS์ ์ต์ํ ํ
โข
๋น ๋ฅธ ๊ฐ๋ฐ ์๋๊ฐ ํ์ํ ๊ฒฝ์ฐ
โข
๋ฒ๋ค ํฌ๊ธฐ ์ต์ ํ๊ฐ ์ค์ํ ๊ฒฝ์ฐ
โข
์ ์ ์คํ์ผ์ด ์ฃผ๋ฅผ ์ด๋ฃจ๋ ๊ฒฝ์ฐ
๋ค๋ฅธ ๋ฐฉ๋ฒ ๊ณ ๋ ค ์ํฉ:
โข
๋์ ์คํ์ผ๋ง์ด ๋ง์ด ํ์ํ ๊ฒฝ์ฐ โ Styled Components
โข
๋ณต์กํ ํ
๋ง ์์คํ
์ด ํ์ํ ๊ฒฝ์ฐ โ CSS-in-JS
โข
๋งค์ฐ ํฐ ๊ท๋ชจ์ ํ๋ก์ ํธ โ CSS-in-JS + ๋์์ธ ์์คํ
๋ง๋ฌด๋ฆฌ
CSS Modules๋ React์์ ์ปดํฌ๋ํธ ๊ธฐ๋ฐ ์คํ์ผ๋ง์ ๊ตฌํํ๋ ๊ฐ๋ ฅํ๊ณ ํจ์จ์ ์ธ ๋ฐฉ๋ฒ์
๋๋ค. ๊ธฐ์กด CSS ์ง์์ ๊ทธ๋๋ก ํ์ฉํ๋ฉด์๋ ๋ชจ๋ ์น ๊ฐ๋ฐ์ ์๊ตฌ์ฌํญ์ ์ถฉ์กฑํ ์ ์์ด, ๋ง์ ํ๋ก์ ํธ์์ ์ฑํ๋๊ณ ์์ต๋๋ค.
ํต์ฌ์ ์ปดํฌ๋ํธ์ ์คํ์ผ์ 1:1 ๋งคํ์ ํตํด ์ ์ง๋ณด์ํ๊ธฐ ์ฌ์ด ์ฝ๋๋ฅผ ์์ฑํ๋ ๊ฒ์
๋๋ค. ์ด ๊ฐ์ด๋์ ์์ ๋ค์ ์ฐธ๊ณ ํ์ฌ ํ๋ก์ ํธ์ ์ ์ฉํด๋ณด์๊ธฐ ๋ฐ๋๋๋ค.