CSS 모듈 소개
일반 CSS 클래스는 모든 CSS 기능을 활용할 수 있다는 장점이 있지만, 클래스 이름이 전역 스코프를 가져 대규모 애플리케이션에서 클래스 이름 충돌(name collision) 문제가 발생할 수 있다는 단점이 있었습니다.
이번 장에서는 이러한 클래스 이름 충돌 문제를 우아하게 해결하고, 컴포넌트 기반 개발 철학에 더 잘 맞는 스타일링 방식인 CSS 모듈(CSS Modules) 에 대해 자세히 알아보겠습니다.
CSS 모듈이란?
CSS 모듈은 CSS 파일을 로드할 때 클래스 이름을 자동으로 고유하게 변환해주는 방식으로, CSS 클래스의 전역 스코프 문제를 해결합니다. 개발자는 평소처럼 클래스 이름을 작성하지만, 빌드 과정에서 이 클래스 이름들이 고유한 해시 값과 조합되어 충돌을 방지합니다.
예를 들어, button
이라는 클래스를 여러 컴포넌트에서 사용하더라도, CSS 모듈을 적용하면 각 컴포넌트의 button
클래스는 실제 HTML에서 Button_button__abc123
와 같이 고유한 이름으로 변환됩니다.
주요 특징
- 지역 스코프 (Local Scope): CSS 클래스가 컴포넌트(파일) 내에서만 유효하도록 만듭니다. 다른 파일의 동일한 클래스 이름과 충돌하지 않습니다.
- 고유한 클래스 이름 생성: 빌드 시 자동으로 고유한 해시를 포함한 클래스 이름을 생성합니다.
- JavaScript 객체로 임포트: CSS 파일이 JavaScript 모듈처럼 임포트되며, 클래스 이름들이 JavaScript 객체의 속성으로 제공됩니다.
create-react-app
으로 생성된 프로젝트에서는 별도의 설정 없이 .module.css
확장자만 사용하면 CSS 모듈 기능을 바로 사용할 수 있습니다. (예: MyComponent.module.css
)
CSS 모듈 사용 방법
CSS 모듈 파일 생성
src/components
폴더에 CssModulesExample.module.css
파일을 만듭니다. 파일 이름에 .module
이 포함되어야 합니다.
/* src/components/CssModulesExample.module.css */
.container {
background-color: #e0ffe0; /* 연한 초록색 배경 */
padding: 25px;
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
text-align: center;
margin-bottom: 25px;
}
.title {
color: #28a745; /* 초록색 */
font-size: 2.3em;
margin-bottom: 12px;
}
.description {
color: #4CAF50;
font-size: 1.15em;
line-height: 1.6;
}
.myButton { /* 카멜 케이스로 작성하는 것이 일반적 */
background-color: #4CAF50;
color: white;
padding: 12px 25px;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 1.1em;
margin: 0 8px;
transition: background-color 0.2s ease, transform 0.1s ease;
}
.myButton:hover {
background-color: #45a049;
transform: translateY(-2px);
}
.primary { /* 조합할 클래스 (일반 클래스처럼 사용) */
font-weight: bold;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
}
컴포넌트에서 CSS 모듈 사용하기
이제 src/components/CssModulesExample.js
파일을 만들고 CSS 모듈을 import하여 사용해 봅시다.
// src/components/CssModulesExample.js
import React from 'react';
import styles from './CssModulesExample.module.css'; // (1) CSS 모듈 import (객체로 불러옴)
function CssModulesExample() {
console.log(styles); // styles 객체가 어떻게 생겼는지 콘솔에서 확인해 보세요!
return (
<div className={styles.container}> {/* (2) styles.클래스이름 형태로 사용 */}
<h2 className={styles.title}>CSS 모듈 예제</h2>
<p className={styles.description}>
이것은 CSS 모듈이 적용된 단락입니다.
</p>
{/* 여러 클래스를 적용할 때는 템플릿 리터럴 사용 */}
<button className={styles.myButton}>기본 버튼</button>
<button className={`${styles.myButton} ${styles.primary}`}>강조 버튼</button>
<p style={{ marginTop: '15px', fontSize: '0.9em', color: '#777' }}>
브라우저 개발자 도구에서 클래스 이름이 고유하게 변경된 것을 확인해 보세요.
</p>
</div>
);
}
export default CssModulesExample;
import styles from './CssModulesExample.module.css';
: CSS 모듈 파일을 import하면, 해당 파일에서 정의된 모든 클래스 이름이styles
라는 JavaScript 객체의 속성으로 제공됩니다.className={styles.container}
: JSX에서className
prop에styles.클래스이름
형태로 접근하여 사용합니다.className={${styles.myButton} ${styles.primary}}
: 여러 클래스를 적용할 때는 템플릿 리터럴(Template Literal)을 사용하여 클래스 이름을 조합할 수 있습니다.
결과 확인
App.js
파일을 수정하여 CssModulesExample
컴포넌트를 렌더링하고 실행해 보세요.
// src/App.js (수정)
import React from 'react';
import './App.css'; // 필요하다면 App.css 유지
import CssModulesExample from './components/CssModulesExample'; // CSS 모듈 예제 불러오기
import InlineStylingExample from './components/InlineStylingExample'; // 이전 장 예제
import CssClassesExample from './components/CssClassesExample'; // 이전 장 예제
function App() {
return (
<div className="App">
<h1>리액트 스타일링 기초</h1>
{/* 기존 예제 유지 또는 주석 처리 */}
{/* <InlineStylingExample />
<hr />
<CssClassesExample />
<hr /> */}
<CssModulesExample />
</div>
);
}
export default App;
브라우저에서 개발자 도구(F12)를 열어 엘리먼트 탭을 확인해 보세요. container
, title
, myButton
등의 클래스 이름이 CssModulesExample_container__abc123
와 같이 파일 이름과 해시 값이 조합된 고유한 이름으로 변환되어 있는 것을 볼 수 있을 것입니다.
CSS 모듈의 장점
- 클래스 이름 충돌 방지: 가장 큰 장점입니다. 각 CSS 클래스가 지역 스코프를 가지므로, 대규모 애플리케이션에서도 클래스 이름 중복으로 인한 스타일 충돌을 걱정할 필요가 없습니다.
- 명확한 의존성: 어떤 컴포넌트가 어떤 CSS 파일에 의존하는지 명확하게 알 수 있습니다.
import
문을 통해 직관적으로 확인할 수 있습니다. - 코드 스플리팅 용이: 웹팩(Webpack)과 같은 번들러는 CSS 모듈을 컴포넌트별로 효율적으로 번들링하고 코드 스플리팅할 수 있게 돕습니다.
- 유지보수 용이: 특정 컴포넌트의 스타일을 변경할 때, 해당 CSS 모듈 파일만 수정하면 되므로 다른 컴포넌트에 영향을 줄까봐 걱정할 필요가 없습니다.
CSS 모듈의 단점 및 고려사항
- 글로벌 CSS 사용의 제약: CSS 모듈은 기본적으로 모든 클래스를 지역 스코프로 만듭니다. 전체 애플리케이션에 적용되어야 하는
body
스타일, 전역 변수(CSS 변수), 또는 외부 라이브러리의 스타일 등을 적용하기 위해서는 별도의 글로벌 CSS 파일을 사용하거나,:global()
셀렉터를 명시적으로 사용해야 합니다./* CssModulesExample.module.css */ /* 전역 클래스를 정의하고 싶을 때 */ :global(.body-scroll-lock) { overflow: hidden; } /* 또는 특정 전역 스타일 적용 */ :global(html), :global(body) { margin: 0; padding: 0; }
- 복잡한 클래스 조합: 동적으로 여러 클래스를 조합해야 할 때, 템플릿 리터럴을 사용하거나
classnames
와 같은 유틸리티 라이브러리의 도움을 받는 것이 일반 CSS 클래스보다 약간 더 번거로울 수 있습니다.import classNames from 'classnames'; // ... const myClasses = classNames( styles.myButton, { [styles.active]: isActive }, // 조건부 클래스 styles.primary // 항상 적용될 클래스 ); return <button className={myClasses}>버튼</button>;
- CSS 변수 접근: CSS 변수(Custom Properties)를 사용하려면 CSS 모듈 파일 내에서 정의해야 합니다. JavaScript에서 동적으로 CSS 변수를 변경하는 것은 인라인 스타일링만큼 직관적이지 않을 수 있습니다.
CSS 모듈과 일반 CSS 파일 혼용
일반적으로 리액트 프로젝트에서는 CSS 모듈과 일반 CSS 파일을 혼용하여 사용합니다.
- 글로벌 스타일:
index.css
나App.css
와 같이 전역적으로 적용되어야 하는 스타일(리셋 CSS, 기본 폰트 설정, 레이아웃 등)은 일반 CSS 파일에 작성합니다. - 컴포넌트 스코프 스타일: 특정 컴포넌트에만 적용되는 스타일은
.module.css
파일을 사용하여 클래스 이름 충돌을 방지합니다.
"CSS 모듈 소개"는 여기까지입니다. 이 장에서는 CSS 모듈의 개념과 사용 방법, 그리고 클래스 이름 충돌 문제를 해결하고 컴포넌트 기반 개발에 적합한 스타일링을 가능하게 하는 CSS 모듈의 장단점을 상세히 다루었습니다.
이제 여러분은 리액트 애플리케이션에서 확장성 있고 유지보수하기 쉬운 스타일링 전략을 선택할 수 있는 중요한 도구 하나를 더 배우셨습니다. 다음 장에서는 자바스크립트 코드 내에서 CSS를 작성하는 방식인 CSS-in-JS 라이브러리에 대해 알아보겠습니다.