
저는 굉장한 @emotion/styled 러버인데요?
그동안 개인 노션에만 저의 사랑을 기록해왔더라구요.
풀어봅니다. 저의 사랑.
" JavaScript로 CSS를 작성하게 해주는 스타일링 라이브러리 "
styled-component와 CSS-in-JS 진영의 양대산맥 중 하나로, styled-component에 영감을 받아 개발했다고 공식 문서에서 직접 밝히고 있다.
즉, Emotion은 styled-components의 오마주라 생각하면 된다.
이번 글은 아래 목차 순으로 작성 예정이다.
목차
1. CSS-in-JS 장점
2. Emotion 작동 원리
3. Emotion 스타일 작성 방식(2가지)
4.@emotion/cssVS@emotion/reactVS@emotion/styled비교
그런데 여기서 의문이 드는 건,
'왜 굳이 CSS 파일 대신 JS로 스타일을 작성할까?' 이다.
CSS-in-JS의 장점을 보면 이해할 수 있다.
1. 스코프 격리 : 클래스명 충돌 걱정이 없이 컴포넌트 단위로 스타일 관리 가능
2. 동적 스타일링 : JS변수, props, state 값을 스타일에 직접 반영 가능
3. Dead Code 제거 : 컴포넌트 삭제 시, 스타일도 함께 삭제.
이러한 장점에 Emotion은 DX(Developer Experience) 향상을 위해 아래 요소를 추가로 제공한다.
"변환된 코드 - 원본 코드를 연결해주는 지도"
빌드 후 브라우저에서 실행되는 코드는 압축/변환되어 있기에, 에러가 발생해도 "어느 파일, 몇 번째 줄에서 발생" 했는지 추적이 어렵다.
이를 해결하는 것이 Source maps이다.
개발자 도구를 통해 어떤 컴포넌트의 어느 줄에서 해당 스타일이 바로 왔는지 확인 가능하다.
Emotion이 생성하는 클래스명은 기본적으로 "해시값"(ex.css-abc123)이라 사람이 읽기 어렵다.
@emotion/babel-plugin을 사용하면, 자동으로 컴포넌트/변수이름을 클래스명에 붙여줘서 어떤 스타일인지 바로 파악이 가능하다.
// label이 없을 때
class = "css-abc123"
//label이 있을 때
class = "css-Button-abc123" // Button 컴포넌트의 스타일임을 바로 인식 가능
직접 label 옵션을 붙일 수도 있다.
const style = css`
label : this-is-label-example;
color : hotpink;
`
// class = "css-this-is-label-example-abc123"
디버깅 시 브라우저 DevTools를 보면 css-this-is-label-example-abc123과 같은 형태를 볼 수 있다.
@emotion/jest 패키지를 통해 Jest 스냅샷 테스트에서 실제 CSS 스타일이 어떻게 적용됐는지 확인할 수 있다.
일반 Jest 스냅샷은 클래스명(css-abc123) 만 찍히는데,
Emotion의 테스트 유틸리티를 쓰면 클래스명이 아닌 실제 스타일 내용이 스냅샷에 포함된다.
// 일반 스냅샷
<button class="css-abc123">클릭</button>
// @emotion/jest 적용 후
.css-abc123 {
color:hotpink;
font-size:16px;
}
<button class="css-abc123">클릭</button>
스타일이 의도치 않게 변경된 경우, 테스트로 바로 잡아낼 수 있어 스타일 회귀 방지에 유용하다.
(오호..Jest 사용 시 적용해봐야겠다)
Emotion은 내부적으로 돌아가는 방식 중 핵심 아래와 같다.
📝 용어 정리
컴파일(Compile) : 코드를 실행 하기 전 변환하는 과정 ( 사람 말을 컴퓨터 말로 번역 )
런타임(RunTime) : 코드가 실제로 실행되는 순간/환경 ( 번역된 코드가 실제로 돌아가는 순간 )
예시 코드를 @emotion/css로 작성해보았다
import { css } from '@emotion/css';
const ExampleStyle01 = css`
color : hotpink;
font-size : 24px;
`
해당 예시 코드는 개발자 모드로 확인하면, 'css-abc123'과 같은 "고유" 클래스명 문자열을 반환한다.
emotion은
1. 스타일 코드를 받아서
2. 해시 기반의 고유 클래스명을 생성하고, 해당 CSS 규칙을
3. <style>태그에 동적으로 주입한다.
이 모든 과정은 @emotion/cache가 담당한다.
emotion은 2가지 스타일 작성 방식을 제공한다.
" CSS 문법 그대로 사용"
const ExampleStyle02 = css`
background-color : hotpink;
color:white;
&:hover{
color:${myColor}; // JS 변수로 사용 가능
}
`
css키워드+백틱(`)을 사용하여 백틱(`) 안에 일반 CSS를 쓰듯 작성한다.
내부적으로 css 함수가 Tagged Template Literal을 파싱 해 처리한다.
📝 용어 정리
파싱(parsing) : 텍스트(문자열)을 읽은 뒤 컴퓨터가 다룰 수 있는 구조로 변환하는 과정.
즉, 글자 덩어리를 의미 있는 구조로 해석하는 것.
" JS 객체로 스타일 표현 "
평소 프로젝트 시 역할별 분리를 좋아하는 나에게는 정말 사랑스러운 표현 방식이다.
이 방식보다는 추후 등장할 @emotion/styled가 정말 압도적이다.
( DX 향상의 길. )
const ExampleStyle03 = css({
backgroundColor : 'hotpink',
color : 'white',
'&:hover':{
color:myColor, // JS 변수
}
})
해당 표현 방식은 CSS 프로퍼티 명이 camelCase로 바뀌는 것만 주의하면 된다.
또한 TypeScript 환경에서 자동완성이 더 잘된다는 이점이 있다.
TS에서 자동완성이 더 잘되는 이유
" CSS 속성명이 JS 객체의 키(Key)이기 때문 "
그러므로 1. Tagged Template Literal의 경우 TS 관점에서 백틱 안은 그냥 문자열이다. 오타가 나도 타입 에러가 발생하지 않고, 자동 완성도 되지 않는다.
2.Object Style의 경우 css( )함수는 인자 타입으로 CSSObject(React의 CSSProperties 기반)를 받는다.
JS객체의 키로 CSS 속성을 사용하기에 TS가 타입 검사를 할 수 있다.
이로 인해 아래와 같은 편리함이 생긴다.
- 자동 완성 :
back입력 시,backgroundColor등 후보 목록이 뜸- 오타 감지 : 존재하지 않는 속성명에 빨간 줄이 생김
- 값 타입 검사 :
font-size:'abc'처럼 잘못된 값 타입을 잡아줌
정리하자면,
" Template Literal 안은 TypeScript가 '문자열'로 보고, Object Style은 TypeScript가 '타입이 있는 객체'로 본다. "
@emotion/cache와 스타일 캐싱에 대해 짧게 먼저 소개를 해보자면,
Emotion은 같은 스타일이 중복으로 삽입되지 않도록 내부적으로 캐시를 사용한다.
동일한 스타일 문자열은 동일한 클래스명을 반환하고, 이미 <style>태그에 존재하면 다시 삽입하지 않는다.
성능 최적화가 내장된 셈이다. ( 최고다...!!!! )
그렇다면, 이제 뭘 써야할까? 라고 할땐
React 기반이라면 개인적으로는 @emotion/styled를 추천한다.
3가지 패키지를 표로 비교를 해보자면 다음과 같다.

" 프레임워크 무관, 가장 심플한 진입점 "
import { css, cx } from '@emotion/css'
const ExampleStyle04 = css`
color : hotpink;
`
<div className={ExampleStyle04}> @emotion/css 예시 코드 </div>
npm install 후 바로 사용 가능css( ) 함수가 클래스명 문자열을 반환(핵심!!)cx( ) 함수를 사용참고 : 공식 문서 예시 출력 화면(🔗 문서 이동)
이런 경우에 선택하는 걸 추천한다.
- React가 아닌 환경(Vue, Svelte, Vanilla JS 등)에서 Emotion을 사용해야 할 때
- Babel 커스터마이징이 불가능한 환경(CRA 기본 설정 등)에서 가장 빠르게 시작하고 싶을 때
" React 유저의 메인 선택지 "
/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react'
const ExampleStyle05 = 'hotpink'
<div css={css`color:${ExampleStyle05};`}> @emotion/react 예시 코드 </div>
📝 용어 정리
보일러플레이트(Boilerplate)
" 기능은 없지만, 꼭 써야하는 의례적인 코드. 매번 반복해서 써야하는 틀에 박힌 코드 덩어리 "
뭔가를 만들기 위해 실제 내용과 상관 없이 항상 작성해야하는 코드.
다다익손.
많으면 피로도와 실수가 증가함
보일러플레이터가 줄어든다를 더 잘 이해하기 위한 비교 예시 코드
// ========== @emotion/react 코드 ==========
// 📄 Button.tsx
/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react'
const Button = () => {
<button css={css`
background : pink;
color : white;
`}>예시 버튼</button>
}
// ===== CSS Module 방식 : 항상 2개의 파일 필요. 반복 작업 증가 =====
// 📄 button.module.css
.button {
background : pink;
color : white ;
}
// 📄 Button.tsx
import styles from './button.module.css' // 매번 import 해야함
const Button = () => {
<button className={styles.button}>css module 버튼</button> // className 연결
}
@emotion/css와 달리 css( )함수가 클래스명 문자열이 아닌 내부 스타일 객체를 반환. 이 객체는 Emotion이 저레벨에서 해석해 다른 스타일과 조합 가능.ThemeProvider를 통해 테마 시스템 사용 가능.JSX Pragma 또는 Babel 설정이 반드시 필요.참고 : 공식 문서 예시 출력 화면(🔗 문서 이동)
이런 경우에 사용하는 걸 추천한다.
- React 프로젝트에서 Babel 설정을 커스터마이징 가능
- styled 컴포넌트를 매번 만들지 않고 요소에 직접 스타일을 적용하고 싶을때
- 테마와 SSR이 중요한 프로젝트
" styled-components에서 갈아타는 사람들의 선택 (마이럽💕)"
styled 패키지는 내부적으로 @emotion/react를 래핑한 API이다.
그래서 @emotion/react의 모든 기능을 포함한다.
래핑 패키지이기에 무조건 @emotion/react가 의존성으로 함께 설치되어야한다.
( 공식 문서 작성된 install 문을 보면 더 와닫는다. )
styled-components와 API가 거의 동일하여 마이그레이션 비용이 낮다.
props 기반 동적 스타일링이 직관적이다.
'as' prop으로 렌더링 태그를 바꾸거나, shouldForwardProp으로 props 전달을 제어하는 등 고급 기능이 포함되어 있다.
참고 : 공식 문서 예시 출력 화면(🔗 문서 이동)
개인적으로 정말 추천한다.
이런 경우에 사용하는 걸 추천한다.
- 팀이 styled-components 스타일 API에 익숙 + 마이그레이션 필요
- 스타일을 독립된 컴포넌트 단위로 분리해서 재사용성을 높이고 싶을 때.
실무에서는 @emotion/react와 @emotion/styled를 함께 쓰는경우가 많다고 합니다.
(클로드 피셜. 아니라면 독자분이 맞아요)
재사용 컴포넌트는 styled로 만들고, 일회성 스타일 조정은 css prop으로 처리하는 식이다.
심지어 둘은 내부적으로 같은 캐시를 공유하므로 동시에 사용해도 충돌도 없다..!!
설명을 통해서도 선택하기가 어려우면 아래 단계를 따라 선택하면 된다.
NO : @emotion/css
YES : Q2 이동
NO : @emotion/css (제한이 있지만 가능)
YES : Q3 이동
CSS props: @emotion/react
styled API : @emotion/styled
Emotion은
" CSS를 JS답게 사용하고 싶다 " + " 그래도 CSS 문법은 익숙한게 좋다 "
를 만족하기 위해 만들어진 라이브러리다.
3개의 패키지 차이를 알고나면, 프로젝트 성격에 맞게 골라 쓰는게 점점 익숙해질 것이다.
( 이렇게 쓰고 난 @emotion/styled를 주로 사용한다. 헿~ )
🔗 참고
Emotion 공식 문서 : https://emotion.sh/docs/introduction