웹 개발에서 스타일링은 UI의 품질과 사용자 경험을 좌우하는 중요한 부분입니다. 이를 구현하는 방법에는 CSS-in-CSS, CSS Module, CSS-in-JS가 대표적입니다. 각각의 방식은 접근 방식과 사용 사례가 다르며, 프로젝트의 요구사항에 따라 선택지가 달라질 수 있습니다
목차
위의 목차를 클릭하면 해당 글로 자동 이동 합니다.
1. CSS-in-CSS
CSS-in-CSS는 가장 전통적인 스타일링 방식으로, 별도의 CSS 파일에 스타일을 작성하고 HTML 요소에 클래스를 할당하는 방식입니다.
특징
- 파일 분리: CSS는 독립적인 파일에 작성되어 HTML, JavaScript와 분리됩니다.
- 전역 네임스페이스: 클래스명은 프로젝트 전역에서 접근 가능합니다.
- 정적 스타일링: 스타일은 런타임에서 변경되지 않고, 빌드 전에 미리 정의됩니다.
장점
- 직관적: 오랜 시간 사용되어 온 방식으로, 배우기 쉽고 이해하기 쉽습니다.
- 성능 최적화: 브라우저의 CSS 캐싱을 활용할 수 있어 성능이 우수합니다.
- 도구와 호환: SCSS, PostCSS, TailwindCSS 등 다양한 CSS 도구와 쉽게 통합됩니다.
단점
- 스타일 충돌 위험: 클래스명이나 스타일이 전역 네임스페이스를 공유하므로 충돌 가능성이 높습니다.
- 컴포넌트 재사용성 제한: 스타일이 파일로 분리되어 컴포넌트와의 결합도가 낮습니다.
- 동적 스타일 관리 어려움: 상태 기반 동적 스타일링이 어렵습니다.
적합한 사용 사례
- 소규모 프로젝트나 정적인 웹사이트.
- CSS 프레임워크(TailwindCSS, Bootstrap 등)를 활용한 빠른 개발.
- 복잡한 동적 스타일링이 필요 없는 프로젝트.
기존의 문서 단위의 웹 페이지 개발방식에서 최근에는 컴포넌트 단위의 웹 애플리케이션(React) 개발 방식으로 바뀌면서 Global Scope를 가지는 기존의 css를 import 하는 방식으로는 개발하는데 어려움이 있습니다.
기존 css는 Global Scope를 가지기 때문에 중복되지 않는 class name을 가져야 하고 JS와의 의존 관계, 컴포넌트의 상태 값에 따른 스타일적용, CSS로드에 따른 우선순위 등 CSS의 구조적인 문제점들을 해결야 합니다.
Global Namespace 문제점
import "./page1.css";
export default function Page1() {
return (
<div>
<h1>페이지1</h1>
<button type='button' className='button'>
버튼
</button>
</div>
);
}
// page1.css
.button {
padding: 10px;
border: 1px solid #000;
background-color: #fff;
font-size: 20px;
font-weight: 700;
color: blue;
}
import "./page2.css";
export default function Page2() {
return (
<div>
<h1>페이지2</h1>
<button type='button' className='button'>
버튼
</button>
</div>
);
}
// page2.css
.button {
padding: 10px;
border: 1px solid #000;
background-color: #fff;
font-size: 20px;
font-weight: 700;
color: red;
}
서버를 실행해 확인해보면 위 사진처럼 css가 개별적으로 적용될까요?
page1과 page2에서 각각 다른 css 파일을 import 했는데 같은 스타일이 적용되었습니다. 그 이유는 CSS 파일이 전역 스코프에서 동작하기 때문입니다. class name이 같다면 다른 파일을 import해도 우선순위에 따라 위와 같은 결과가 나오게 됩니다. 이런 CSS의 문제점을 해결하려고 나온 도구들이 CSS-Module, CSS-in-JS입니다.
2. CSS Module
CSS Module은 CSS 파일을 모듈화 하여 컴포넌트 단위로 스타일을 관리할 수 있는 방식입니다. 클래스명이 자동으로 고유화되어 스타일 충돌을 방지합니다. (파일명.module.css 와 같은 파일을 만들어서 각 컴포넌트마다 import 해서 사용)
동작 방식
위 그림에서 볼 수 있듯이, CSS 모듈 컴파일 과정을 통해 [파일명]_[클래스명]_[랜덤 해시] 형태의 class name을 만들어 냅니다.
사용법
1. [파일명].module.css 파일 생성
2. import styles from [파일 경로]
ex) import styles from '@/styles/page.module.css'
3. className 지정
ex) <div className={styles.navigation}>Navigation</div>
특징
- 로컬 스코프: 각 CSS 클래스는 컴포넌트 범위에서만 유효합니다.
- 파일 기반: CSS는 여전히 별도의 파일에 작성되지만, 빌드 과정에서 고유한 클래스명이 생성됩니다.
장점
- 스타일 충돌 방지: 클래스명이 고유화되므로 전역 네임스페이스 문제를 해결합니다.
- CSS 활용: 기존 CSS 문법과 도구를 그대로 사용할 수 있습니다.
- 컴포넌트 중심: 컴포넌트 단위로 스타일을 관리하므로 재사용성과 유지보수가 높아집니다.
단점
- 동적 스타일링 한계: props나 state를 기반으로 하는 동적 스타일링은 JavaScript 코드에서 추가 작업이 필요합니다.
적합한 사용 사례
- 컴포넌트 기반 UI 라이브러리(React, Vue)에서 스타일 충돌을 방지하고자 할 때.
- CSS와 기존 스타일링 방식에 익숙한 팀이 있는 경우.
- 정적인 스타일링과 컴포넌트 중심 관리가 중요한 프로젝트.
CSS Module을 이용한 Global Namespace 문제점 해결
import styles "./page1.module.css";
export default function Page1() {
return (
<div>
<h1>페이지1</h1>
<button type='button' className={styles.button}>
버튼
</button>
</div>
);
}
// page1.module.css
.button {
padding: 10px;
border: 1px solid #000;
background-color: #fff;
font-size: 20px;
font-weight: 700;
color: blue;
}
import styles "./page2.module.css";
export default function Page2() {
return (
<div>
<h1>페이지1</h1>
<button type='button' className={styles.button}>
버튼
</button>
</div>
);
}
// page2.module.css
.button {
padding: 10px;
border: 1px solid #000;
background-color: #fff;
font-size: 20px;
font-weight: 700;
color: blue;
}
3. CSS-in-JS
CSS-in-JS는 JavaScript 코드 안에서 스타일을 정의하고 관리하는 방식입니다. css-module을 사용해 global scope로 인한 class name 중복에 대한 문제를 해결했지만, 프로젝트 규모가 커질수록 많은 양의 style 파일을 따로 관리해야 하는 불편함을 해결하기 위해 등장했습니다.
동작 방식
1. classname 동적 생성
- CSS-in-JS에서는 각 컴포넌트를 hashing하여 동적인 className("sc-cMWNzn")을 만듦.
- 컴포넌트가 생성될 때 head tag에 style tag로 같이 생성되며, 사라질 때도 같이 사라짐.
- styled-components의 경우, 같은 컴포넌트가 여러 개 생성될 때는 하나의 css만 사용됨(최적화).
2. css parser
- 각 CSS-in-JS 엔진은 style html tag를 만드는 CSS parser를 내장하고 있음.
- styled-components는 stylis라는 CSS preprocessor를 이용.
특징
- 스타일과 로직 결합: 컴포넌트 내부에서 props, state를 사용해 동적으로 스타일을 정의할 수 있습니다.
- 로컬 스코프: 스타일이 컴포넌트 범위에서만 유효합니다.
- 동적 스타일링: 조건부 스타일링과 테마 적용이 유연하게 가능합니다.
장점
- 동적 스타일 관리 용이: 상태 기반 스타일링을 쉽게 구현할 수 있습니다.
- 컴포넌트 단위 관리: 스타일과 로직이 한 파일에 있어 코드 모듈화와 재사용성이 높아집니다.
- 테마 관리: 전역 상태 관리 도구와 결합하여 다크모드 등 테마를 쉽게 구현할 수 있습니다.
단점
- 런타임 오버헤드: 런타임에서 스타일을 생성하고 DOM에 삽입하므로 성능에 영향을 미칠 수 있습니다.
- CSS 생태계와의 단절: 기존 CSS 도구와의 호환성이 낮아 별도의 학습 곡선이 필요합니다.
- 파일 크기 증가: 스타일과 로직이 결합되어 코드 파일이 커질 수 있습니다.
적합한 사용 사례
- 상태 기반 동적 스타일링이 빈번히 발생하는 프로젝트.
- React, Vue 등 컴포넌트 중심 개발 방식에서 유연한 스타일링이 필요한 경우.
- 다양한 테마 적용이 중요한 프로젝트(예: 다크모드, 사용자 지정 테마).
CSS-in-JS(styled-component)을 이용한 Global Namespace 문제점 해결
// Button.jsx
import styled from "styled-components";
const StyledButton = styled.button`
padding: 10px;
border: 1px solid #000;
background-color: #fff;
font-size: 20px;
font-weight: 700;
color: ${(props) => props.color};
`;
export default function Button({ children, color }) {
return <StyledButton color={color}>{children}</StyledButton>;
}
import Button from "./Button";
export default function Page1() {
return (
<div>
<h1>페이지1</h1>
<Button color={"blue"}>버튼</Button>
</div>
);
}
import Button from "./Button";
export default function Page2() {
return (
<div>
<h1>페이지2</h1>
<Button color={"red"}>버튼</Button>
</div>
);
}
4. CSS Module, CSS-in-JS 비교
특징 | CSS Module | CSS-in-JS |
스타일 작성 위치 | 별도의 CSS 파일 | JavaScript 코드 내부 |
번들 크기 | 작음 | 상대적으로 큼 |
동적 스타일링 | 제한적 | 매우 유연 |
런타임 성능 | 빌드된 CSS 파일을 캐싱해 뛰어남 | 추가 오버헤드 발생 가능 |
도구 호환성 | 기존 CSS와 호환 | 제한적 호환 |
학습 곡선 | 낮음 | 높음 |
CSS Module:
- 초기 로드 속도: 스타일이 미리 컴파일되고 브라우저가 직접 처리하므로 자바스크립트 런타임 부담이 적습니다.
- JS 번들 크기 감소: 스타일이 자바스크립트 코드에 포함되지 않기 때문에 런타임 메모리 사용량이 상대적으로 낮습니다.
- SEO 친화적이며, 정적 스타일링을 선호하는 경우 적합
CSS-in-JS
- 재사용성: 동적으로 생성된 스타일은 다른 플랫폼에서도 쉽게 재사용할 수 있습니다.
- 성능 부담: 런타임 시 스타일 생성으로 인해 초기 렌더링 속도가 CSS Module보다 느릴 수 있으며, 특히 대규모 애플리케이션에서 JS 번들 크기가 커질 가능성이 높습니다.
- 스타일의 동적 생성이 필요한 프로젝트(예: 테마 전환 또는 고도로 인터랙티브 한 UI)에 적합
'개발 공부 일지 > CSS' 카테고리의 다른 글
Position 속성의 퍼포먼스 이슈 (3) | 2024.09.09 |
---|---|
반응형 웹 (0) | 2024.09.09 |
CSS 적용 방법 및 Cascading의 원리 (0) | 2024.09.09 |
Cascading (0) | 2024.09.09 |