..

Search

18) styled-components

styled-components


CSS-in-JS

CSS가 등장한 이후 웹 앱은 점점 더 복잡해지고 동적인 기능들의 요구 또한 꾸준히 증가해 왔습니다. 이에 따라 HTML과 CSS만으로는 사용자 View에 대한 모든 스타일링 요구를 처리할 수 없는 상황에 이르게 되었습니다.

 

이를 해결하기 위해 성능과 효율을 위한 다양한 라이브러리와 프레임워크가 등장하게 되었으며, 우리가 앞서 살펴 본 Sass와 같은 CSS 전처리기를 사용하는 방식과 CSS Module처럼 CSS를 모듈화하여 사용하는 방식 등이 개발되었습니다.

 

하지만 위와 같은 CSS-in-CSS 방식과는 다른 관점에서 이러한 문제점을 해결하려는 시도가 등장합니다. 2014년 페이스북의 개발자였던 Christopher Chedeau(vjeux)에 의해 자바스크립트(JS) 코드 안에 CSS 코드를 함께 작성하는 방식인 CSS-in-JS에 대한 개념이 제시되었습니다.


CSS-in-JS의 특징

vjeux는 기존의 CSS 스타일링 방식이 다음과 같은 문제들로 인해 사용이 점차 어려워지고 있다고 설명합니다.

 

1. Global namaspace : CSS는 전역 스코프에 모든 스타일을 작성하기 때문에 CSS 클래스명에 대한 별도의 명명 규칙이 필요합니다.

2. Dependencies : 하나의 CSS 스타일이 여러 HTML 요소에 적용될 수 있으며, 반대로 하나의 HTML 요소에 여러 CSS 스타일이 적용될 수도 있습니다. 이 모든 관계는 명시적이지 않기 때문에 개발자가 이 모든 의존 관계를 기억해야만 합니다.

3. Dead Code Elimination : CSS 코드의 의존 관계는 명시적이지 않기 때문에 현재 사용되고 있지 않은 코드의 파악이 쉽지 않으며, 이에 따라 기능 변경에 따른 유지보수가 힘듭니다.

4. Sharing Constants : JS와 CSS 파일이 서로 분리되어 있기 때문에 JS의 상태 값을 CSS에 공유하기 어렵습니다.

5. Non-deterministic Resolution : 로드 순서에 따라 CSS의 우선 순위가 변경되므로, CSS의 로드 순서까지 관리해야만 합니다.

 

이에 vjeux는 CSS-in-JS라는 개념을 제시하면서, 이를 사용하면 위에서 언급한 문제들이 다음과 같이 해결될 것이라고 설명했습니다.

 

1. Global namaspace : CSS 스타일이 지역 스코프로 한정되므로, 별도의 명명 규칙이 필요하지 않습니다.

2. Dependencies : CSS가 컴포넌트 레벨에서 모듈화되므로, CSS 간의 의존 관계를 고려할 필요가 없습니다.

3. Dead Code Elimination : CSS가 컴포넌트와 같은 파일에 존재하므로, 사용되지 않는 코드를 간단히 파악할 수 있습니다.

4. Sharing Constants : JS 코드와 CSS 코드가 한 파일에 존재하므로, 상태나 함수를 서로 쉽게 공유할 수 있습니다.

5. Non-deterministic Resolution : HTML과 CSS를 강하게 결합시킴으로써, 현재 렌더링된 HTML에 적용된 CSS가 무엇인지 언제나 알 수 있습니다.

 

이와 같은 새로운 패러다임의 등장으로 CSS-in-JS 개념을 구현한 수많은 라이브러리가 등장했으며, 대표적인 라이브러리로는 styled-components, Emotion, styled-jsx, vanilla-extract 등이 있습니다.

 

이번 장에서는 React에 styled-components 라이브러리를 적용하는 방법 위주로 설명할 예정이므로, styled-components에 대해 좀 더 자세히 알고 싶다면 공식 문서를 참고하시기 바랍니다.

◎ styled-components 공식 홈페이지 (https://styled-components.com/)


styled-components

styled-components는 React에서 사용되고 있는 CSS-in-JS 방식의 라이브러리 중에서 가장 많이 사용되고 있는 라이브러리입니다.

 

React에서 styled-components를 사용하기 위해서는 우선 styled-components 라이브러리를 설치해야 합니다.

Shell

# npm인 경우
npm install styled-components


# yarn인 경우
yarn add styled-components

 

styled-components를 사용하기 위해서는 우선 styled-components 패키지를 불러와야 합니다.

styled-components는 HTML 요소나 React 컴포넌트에 원하는 스타일을 적용할 때 사용되며, 스타일링 대상에 따라 사용 방법이 약간 다릅니다.

 

첫 번째로 HTML 요소의 경우에는 기본적인 HTML 요소에 대한 속성은 미리 정의되어 있기 때문에 해당 태그명을 그대로 사용하여 CSS 스타일을 정의할 수 있습니다.

HTML 요소의 스타일링

styled.[태그명]`CSS 스타일`;

 

다음 예제는 작성한 CSS 스타일이 적용된 새로운 HTML <button>요소를 생성하여 ButtonOne이라는 이름의 컴포넌트로 저장하는 예제입니다.

예제(App.js)
import styled from "styled-components";
//...
const ButtonOne = styled.button`
  padding: 6px 12px;
  margin: 10px;
  border: 2px solid orange;
  color: orange;
  font-size: 1rem;
`;
//...
<div>
  <ButtonOne>오렌지색 버튼</ButtonOne>
</div>

 

두 번째로 React 컴포넌트의 경우에는 특정 컴포넌트를 인수로 전달함으로써 해당 컴포넌트의 CSS 스타일을 손쉽게 확장하여 사용할 수 있습니다. 즉, 인수로 전달된 컴포넌트의 스타일은 모두 그대로 상속 받은 채 특정 스타일을 추가하거나 변경한 컴포넌트를 손쉽게 생성할 수 있습니다.

React 컴포넌트의 스타일링

styled([컴포넌트명])`CSS 스타일`;

 

다음 예제는 인수로 전달받은 ButtonOne 컴포넌트의 모든 스타일을 상속 받고, 그 중에서 border 속성과 color 속성만을 다른 스타일로 변경한 ButtonTwo 컴포넌트를 생성하는 예제입니다.

예제(App.js)
//...
const ButtonOne = styled.button`...`
//...
const ButtonTwo = styled(ButtonOne)`
  border: 4px solid green;
  color: green;
`;
//...
<div>
  <ButtonOne>오렌지색 버튼</ButtonOne>
  <ButtonTwo>초록색 버튼</ButtonTwo>
</div>


Tagged 템플릿 리터럴

위의 예제에서 우리는 CSS 스타일을 백틱(`) 문자를 사용하여 문자열 형태로 작성하였습니다.

이 방식은 CSS Module에서 살펴 본 템플릿 리터럴과 아주 비슷하지만, 커스텀 tag 함수를 사용하여 템플릿에 추가한 자바스크립트 객체나 함수를 컴포넌트에서 온전히 추출할 수 있다는 점이 다릅니다. 이를 Tagged 템플릿 리터럴이라고 합니다.

 

템플릿 리터럴에서 문자열 인터폴레이션(string interpolation)을 사용하여 객체나 함수를 추가하면 해당 객체나 함수는 단순 문자열로 치환되어 저장됩니다. 하지만 Tagged 템플릿 리터럴에서는 템플릿 안에 넣은 객체나 함수를 원래대로 다시 추출할 수 있습니다.

 

이와 같은 특성을 활용하여 컴포넌트에서 전달한 props 값을 스타일에서 손쉽게 참조하여 사용할 수 있으며, 이를 통해 컴포넌트에서 넘어온 props 값에 따라 서로 다른 스타일을 적용할 수 있습니다.

 

다음 예제는 props를 통해 서로 다른 color 속성값을 전달하여 서로 다른 스타일의 컴포넌트를 생성하는 예제입니다.

예제(App.js)
//...
const ButtonOne = styled.button`
  padding: 6px 12px;
  margin: 10px;
  border: 2px solid ${(props) => props.color};
  color: ${(props) => props.color};
  font-size: 1rem;
`;
//...
<div>
  <ButtonOne color="red">빨간색 버튼</ButtonOne>
  <ButtonOne color="orange">주황색 버튼</ButtonOne>
</div>
//...


css prop를 활용한 조건부 스타일링

Tagged 템플릿 리터럴을 사용하면 props 값에 따라 서로 다른 스타일의 React 컴포넌트를 생성할 수 있습니다.

 

하지만 props에 따라 바꾸고 싶은 CSS 속성이 하나가 아니라 여러 개일 경우에는 styled-components에서 제공하는 css prop을 사용하여 여러 개의 CSS 속성을 하나로 묶어서 정의할 수 있습니다. css prop을 사용하기 위해서는 먼저 styled-components 패키지에서 css를 불러와야 합니다.

 

다음 예제는 props를 통해 전달되는 bold 값이 true인 경우에만 글씨 굵기와 테두리 굵기를 모두 두껍게 스타일링한 컴포넌트를 생성하는 예제입니다.

예제(App.js)
import styled, { css } from "styled-components";

const ButtonOne = styled.button`
  padding: 6px 12px;
  margin: 10px;
  border: 2px solid ${(props) => props.color};
  color: ${(props) => props.color};
  font-size: 1rem;

  ${(props) =>
    props.bold &&
    css`
      color: ${(props) => props.color};
      font-weight: 600;
      border: 4px solid ${(props) => props.color};
    `}
`;
//...
<div>
  <ButtonOne color="red">빨간색 버튼</ButtonOne>
  <ButtonOne color="orange" bold>
    주황색 버튼
  </ButtonOne>
</div>
//...

 

<ButtonOne color="orange" bold>와 <ButtonOne color="orange" bold=”true”>는 같은 의미입니다. 이처럼 props의 값을 생략하면, 해당 값을 true라고 간주합니다.

CSS-in-JS의 단점

이렇게 다양한 장점을 가지고 있는 CSS-in-JS 방식도 다음과 같은 문제점을 가지고 있습니다.

1. CSS-in-JS는 컴포넌트가 렌더링될 때 자바스크립트 코드의 해석 과정이 필요하므로, 추가적인 CPU의 런타임 오버헤드(runtime overhead)가 발생합니다.

2. CSS-in-JS 방식으로 구현된 React 웹 앱을 방문하는 사용자들은 해당 라이브러리를 다운로드해야만 하므로, 번들의 크기가 늘어나고 렌더링 속도가 저하됩니다.

3. CSS-in-JS의 각 속성들이 React DevTool에 포함되므로, 디버깅할 때 DevTool을 통한 코드의 가독성이 저하됩니다.

 

우리는 지금까지 React에서 사용할 수 있는 다양한 스타일링 방식에 대해 살펴보았습니다. 이 중에는 기존 방식의 단점을 개선한 도구도 있었으며, 전혀 새로운 개념을 바탕으로 개발된 도구도 있었습니다. 하지만 모든 상황에서 항상 장점만을 가지고 있는 도구는 없으며, 각 프로젝트의 상황과 특성을 고려하여 가장 알맞은 스타일링 방식을 사용하는 것이 중요합니다.


연습문제