성장, 그리고 노력

부족하더라도 어제보다 더 잘해지자. 노력은 절대 배신하지 않는다.

Javascript

CSS-in-JS

제이콥(JACOB) 2020. 8. 19. 04:43

출처: oliverturner's twitter

 

프론트엔드 개발자로 있다 보면, 한 번쯤은 들어봤을 용어이다. 사실 CSS도 알고 JS도 이미 알고 있다. 그렇다면 직역을 해보자면,  자바스크립트 안에 있는 CSS 정도일 것이다. 이것도 당연 이해가 된다. 언뜻 보면 어렵지 않아 보이는 이 개념이 왜 궁금한 것일까. 조금 더 깊게 파보자.

 

기존 CSS 사용 방식에는 어떤 문제가 있었을까?

 css는 기본적으로 전역으로 사용된다. 그래서 우리는 class name을 통해 CSS를 제어하기 시작했다. 또한 한 엘리먼트(element)에 여러 개의 클래스 이름을 붙였다.

참고로 className은 class와 동일하다. class 이름이라고 부르기가 애매해서 className이라고 적었다.

 

아래 간단한 예를 보자.

 만약 버튼의 배경색을 파란색으로 해달라는 요구사항이 생겼다면, 아래와 같이 css를 작성하면 되고, primary 클래스가 있다면 배경색을 파란색으로 적용될 것이다.

.primary {
  background: blue;	
}

  여기까지는 아무 문제가 없다. 하지만 얼마되지 않아 밝은 파란색 버튼을 만들어 달라는 다음 요구 사항이 생겼다. 그래서 우리는 또 하나의 클래스(. light)를 만들었다.

.primary {
  background: blue;
}
...
...
...
.primary.light {
  background: lightblue;
}

 물론 잘 작동한다. 이렇게 하다 보니 생긴 문제는 이제 모든 것이 전역적(global)로 사용되다 보니, class name 대한 중복(충돌)을 신경 써야 했다. 프로젝트가 작으면야 크게 문제 되지는 않지만, 규모가 클수록 이러한 문제는 더 커진다. 

적용 또한 잘된다.

 위에서 말한 것이 잘 와닿지 않을 수 있다. 그럼 다음 요구사항까지 해결해보자. 

이번에는 위에 요구사항이나 내용을 모르는 다른 팀 개발자에게 파란색 제목을 만들어 달라는 요구 사항이 왔다. 그래서 옆 개발자는 h1 태그에 파란색을 입히려고 className으로 primary를 사용했다. 어떻게 되었을까?

 h1 태그에 className을 하나 추가했을 뿐인데 뜬금없이 배경색과 글자색이 변하는 놀라운 경험을 했다. 이미 CSS가 전역적으로 사용된다고 알았던 다른팀 개발자는 서둘러 className을 blue로 바꿔서 CSS를 다시 정의하였다.

// <h1 className="blue">제목</h1>

.blue {
  color: blue;
}

 당연히 잘 적용된다. 이런 과정을 반복하다 보면 점점 className 충돌은 더 많이 발생하고 이것을 참다 참다 포기한 개발자는 아래와 같은 코드로 전쟁을 선포하기 시작한다.

.blue {
  color: blue !important;
}

  Ah... important 라니... 이제 끝나지 않는 전쟁으로 치닫고 점점 간단한 CSS를 리펙토링 하는 것조차 힘들어지기 시작한다. "className을 좀 더 체계적으로 의미 있게 관리할 수는 없을까?"라는 의문이 생기기 시작했고 그래서 BEM 같은 명명 규칙이 등장하였다.

 

  BEM (Block, Element, Modifier)은 개발자가 HTML과 CSS의 관계를 더 잘 이해하도록 도와준다. 좀 더 쉽게 말해 "의미 있는 클래스 이름"을 짓는 걸 도와주는 규칙이다. 잠시 BEM에 대해서 조금만 이해하고 넘어가자.


BEM

.Block__element--modifier

 먼저 Block이라는 개념을 알아야 한다. BEM에서 말하는 Block은 최상위 추상화 단계에 있는 어떤 요소라고 보면 된다. 

위의 예제 코드를 예로 들자면, BEM 형식으로 Block을 나타낸다고 했을 때 아래와 같이 바꿀 수 있다.

// 기존
<button>기본 버튼</button>
<button className="primary">동의 버튼</button>
<button className="primary light">옅은 버튼</button>
<h1 className="blue">제목</h1>

// BEM
<button className="btn">기본 버튼</button>
<button className="btn__agree">동의 버튼</button>
<button className="btn--primary--light">옅은 버튼</button>
<h1 className="title--blue">제목</h1>

 여기서 블록(Block)은 'btn', 'title'이다. 그 자체로 의미 있는 것들이며 블록 자체는 중복될 수도 있다. 참고로 Get BEM에서는 아래와 같이 블록을 정의한다.

Encapsulates a standalone entity that is meaningful on its own. While blocks can be nested and interact with each other, semantically they remain equal; there is no precedence or hierarchy. Holistic entities without DOM representation (such as controllers or models) can be blocks as well.

 Element는 블록 뒤에 __ (밑줄 2개) 뒤에 나오는 것을 말한다(btn__agree). 블록의 일부이며 독립적인 의미는 없지만, 모든 Element는 의미상 블록과 연결되게 된다. 

 

 마지막으로 Modifer는 block 또는 element에 대한 플래그, 모양, 동작, 상태를 변경하는 데 사용되며, 블록 또는 엘리먼트의 뒤 --(대시 2개)로 구분한다(title--blue). 

 

이 블록은 단일로도 명명 가능하며, 나머지 두 요소는 블록과 같이 명명된다. 물론 세 개를 모두 한 번에 적용할 수도 있다(예를 들어, btn--agree__disabled). 


 BEM 방식으로 클래스 이름의 중복을 막으면서, 의미 있는 클래스명을 지었다고 하자. 하지만 "클래스 이름"과 "스타일"이 적용되는 부분은 여전히 별도로 위치해 있고, CSS는 기본적으로 전역으로 적용되기 때문에 관련 없는 부분까지 스타일이 지정될 수도 있다. 이는 우리가 CSS를 리펙토링 하는 것을 힘들게 만든다.

 

 이런 문제들을 해결하려고 CSS를 JS로 가져오게 되었다. 그리고 탄생한 것이 CSS in JS이다. 자바스크립트의 강력함 언어의 특징을 이용해서 CSS를 좀 더 효과적으로 다루려고 한 것이다. CSS in JS에서는 어떻게 위에 문제들을 해결하는지는 차근차근 살펴보자.


아니, 그럼 관심사 분리(SOC)는 어쩔 건데?

 이야기를 더 하려다 보니, 이런 의문이 생긴다. 우리는 항상 관심사 분리라는 말을 입에 달고 산다. 실제로도 중요하고 이미 수많은 개발 명서와 개발자들에 의해서 중요성은 검증되었다고 해도 과언이 아니다. 자바스크립트랑 CSS를 같이 사용한다니... 이게 무슨 관심사 분리를 지키지 않는 무책임한 말인가!라고 할 수 있지만, CSS in JS를 말하는 사람들은 조금 다른 멋진 논리로 이 논쟁을 종식시킨다.

출처: https://speakerdeck.com/didoo/let-there-be-peace-on-css?slide=62

  "관심사 분리를 기술의 분리랑은 다르다!" 


CSS in JS

CSS-in-JS is a styling technique where JavaScript is used to style components.  - Wikipedia -

이제 다시 CSS in JS 이야기를 해보자. 

 CSS in JS는 자바스크립트 구문으로 CSS를 작성할 수 있게 해 준다. 자바스크립트 코드로 헬퍼 함수를 만들어 스타일을 쉽게 조작할 수 있으며, 상수를 만들어 공유할 수도 있다. SASS나 LESS도 충분히 멋진 기술들이지만, 그런 것들을 더 익힐 필요 없이 자바스크립트로 모두 해결할 수 있다.

 className의 경우 개발자가 고민할 필요가 없다. 내부적으로 고유한 해시 값을 생성하고, 그 해시값으로 된 스타일 시트를 생성해 준다. 의미 있는 이름을 짓기 위해 시간을 더 이상 쏟지 않아도 되는 것이다. 

 CSS의 대부분의 문법들을 그대로 사용할 수 있다. :hover, :focus 같은 pseudo class, 미디어 쿼리, flexbox, 애니메이션 등 모두 사용할 수 있다. 

 같은 이름은 마음껏 써도 된다. CSS in JS를 사용하면 전역 스타일이 아니다. 게다가 className으로 BEM 방식을 통해 의미를 전달한 것보다 더 직관적이고 의미를 가질 수도 있다. 

// 기존 방식
<button className="primary">파란 버튼</button>
<button className="primary">동의 버튼</button>

// CSS in JS
<BlueButton>파란 버튼</BlueButton>
<AgreeButton>동의 버튼</AgreeButton>

 또한 styled-components, emotion 같은 멋지고 안정적인 라이브러리도 이미 제공되고 있으며, 커뮤니티나 사례들은 충분히 퍼져있다. 

 

JS로 끝내면 좋다고 했으니, 그냥 style을 Inline으로 다 넣어버리면 되지 않나?

이렇게 하지 않는 이유는 크게 두 가지 정도가 있다.

 

1.  pseudo class를 사용할 수 없다. 서드 파티 라이브러리를 이용하면 가능한 경우도 있긴 하다. 

// Wow...
<button
  style={{
    "&:hover": {
	  background: "red"
	}
  }}
>기본 버튼</button>

2. 대부분의 경우 성능이 안 좋다.

 인라인 스타일을 사용하면, 브라우저가 돔을 그릴 때 스타일 요소까지 모두 그려야 한다. 이렇게 되면 해당 엘리먼트의 CSS 성능 자체는 좋아질지 몰라도(CSS 엔진이 CSS 선택자와 일치하는 요소를 찾는 조회시간이 줄어들기 때문에), 브라우저에서 CSS 파일을 별도로 캐싱할 수 없기 때문에 페이지를 로드할 때마다 서버에서 로드되는 양이 늘어난다. 또한 반복되는 인라인 CSS 구문으로 인해 HTML 파일이 CSS 파일보다 몇 배 더 커진다. 

 하지만 여기서 "대부분"이라고 한 이유는 동적인 애니메이션을 구현할 때는 인라인 스타일이 성능상 더 좋은 경우가 있기 때문이다.

 

 

 

반응형