Search
Duplicate

React CSS module

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 ๋งคํ•‘์„ ํ†ตํ•ด ์œ ์ง€๋ณด์ˆ˜ํ•˜๊ธฐ ์‰ฌ์šด ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด ๊ฐ€์ด๋“œ์˜ ์˜ˆ์ œ๋“ค์„ ์ฐธ๊ณ ํ•˜์—ฌ ํ”„๋กœ์ ํŠธ์— ์ ์šฉํ•ด๋ณด์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค.