자바스크립트

[번역] CSS의 진화 : CSS 부터 SASS, BEM, CSS 모듈, 스타일드 컴포넌트 까지

원문 : https://m.alphasights.com/css-evolution-from-css-sass-bem-css-modules-to-styled-components-d4c1da3a659b

인터넷이 시작된 이후로 우리는 항상 웹사이트를 꾸며야 했고, CSS는 언제나 우리 주변에 있으면서 몇년동안 자신만의 페이스로 진화해 왔다. 이 글은 여기에 대한 자세한 내용을 여러분에게 알려주려고 한다.

먼저 우리는 CSS가 무엇인지에 대해서 공감대를 형성해야 하는데, CSS가 마크업 언어로 쓰여진 문서의 표현을 기술하기 위해 사용된다는 점에 대해서는 우리 모두 동의하고 있을거라 생각한다.

CSS가 꾸준히 진화해 왔으며 최근에 더욱 강력해졌다는 것은 새로울 것이 없지만, CSS를 팀단위의 작업에서 사용하기 위해서는 부가적인 도구가 필요하다는 것도 다들 알고있을 것이다.

CSS 서부 개척 시대 (wild-west)

90년대에는 “장식적인” 인터페이스를 만들어내는 것에 집중했고, 이목을 끄는 것이 가장 중요한 일이었는데, 이를 위해 인라인 스타일이 사용되었다. 우리는 페이지가 다르게 보여지는 것에 대해서는 신경쓰지 않았으며, 결국 웹페이지들은 몇가지 GIF 이미지나 마퀴(marquee), 혹은 다른 끔직한 (당시로선 인상적이었던) 요소들을 던져넣어서 방문자들의 이목을 끌려고 했던 귀여운 장난감 같았다.

Microsoft's first website

그 이후에 우리는 동적인 사이트들을 만들기 시작했지만, CSS는 여전히 복잡한 채로 남아있었고, 모든 개발자들이 저마다의 방식으로 CSS를 작성했다. 우리들 중 몇몇은 새로운 코드를 작성했을 때 기존 스타일이 영향을 받는 문제 때문에 고생했고, !important를 사용해서 UI요소가 특정한 방식으로 보여지는 것을 강제할 수 있었다. 하지만 우리는 곧 깨달았다 :

!important Does not fix your bad css

이러한 모든 시도들은 프로젝트의 규모나 복잡도가 커져갈수록 혹은 팀원들의 수가 많아질수록 더 명백하고 큰 문제들이 되었다. 스타일링을 위한 일관된 패턴이 없다는 것은 CSS를 작성하는 올바른 방법을 찾으려고 노력하던 개발자들에게 가장 큰 걸림돌이었다. 결국은 올바르거나 나쁜 방식이란 없었고, 우리는 그저 페이지가 제대로 표시되는 것에만 신경쓰게 되었다.

CSS

SASS 의 구원

SASS는 CSS를 네스팅(nesting), 변수, 믹스인, 확장, 로직 등을 장착한 전처리 엔진 형태의 프로그래밍 언어로 변형시켰다. 이를 통해 CSS 파일들을 잘 구조화할 수 있게 되었고, 최소한 CSS 파일을 몇개의 작은 파일로 분리할 수 있는 방법이 생겼다. 당시로선 엄청난 일이었다.

SASS는 본질적으로 SCSS 코드를 읽어서 전처리한 다음 컴파일 해서 전역 CSS 번들 파일을 만들어준다. 멋지지 않은가? 하지만 꼭 그렇지만은 않았다. 얼마 지나지 않아, 전략이나 모범 사례 없이는 SASS가 문제를 해결해준다기 보다는 더 많은 문제를 만들어낸다는 것이 명백해졌다.

결국 우리는 전처리기가 내부에서 어떤 작업을 하는지는 알지 못한 채 스타일이 겹치는 문제를 해결하기 위해 단순히 계층 구조를 만들어 내는 것에 의지하게 되었고, 그 결과 컴파일된 CSS는 어마어마하게 커지게 되었다.

BEM이 등장하기 전까지 말이다…

BEM과 컴포넌트 기반 사고

BEM의 등장은 우리가 재사용성과 컴포넌트에 대해서 생각할 수 있게 해 주는 신선한 공기였다. BEM은 새로운 수준의 의미를 부여할 수 있게 해 주었고,
단순한 블록(Block), 요소(Element), 변경자(Modifier) 컨벤션을 사용함으로써 클래스명이 유일함(unique)을 보장하여 스타일이 겹치는 위험성을 줄일 수 있게 해 주었다.

다음의 예제를 보자.

<body class="scenery">
  <section class="scenery__sky">
    <div class="sky [sky--dusk / sky--daytime] [sky--foggy]">
      <div class="sky__clouds">div>
      <div class="sky__sun">div>
    div>
  section>
  <section class="scenery__ground">section>
  <section class="scenery__people">section>
body>

마크업을 조금 분석해보면 블록, 요소, 변경자 컨벤션이 적용되고 있는 것을 볼 수 있을 것이다.

위의 코드에는 매우 명시적인 두 개의 블록: .scenery와 .sky이 있으며 각각은 자신만의 블록을 가지고 있다. Sky는 유일하게 수정자를 갖고 있는데, foggydaytime 혹은 dust가 될 수 있으며 이들은 각기 다른 특성이 같은 요소에게 적용될 수 있도록 한다.

이 마크업에 적용되는 CSS를 살펴보면 좀더 잘 이해할 수 있을 것이다.

// Block
.scenery {
   //Elements
  &__sky {
    fill: screen;
  }
  &__ground {
    float: bottom; 
  }
  &__people {
    float: center;
  }
}

//Block
.sky {
  background: dusk;
  
  // Elements
  
  &__clouds {
    type: distant;
  }
  
  &__sun {
    strength: .025;
  }
  
  // Modifiers
  &--dusk {
    background: dusk;
    .sky__clouds {
      type: distant;
    }
    .sky__sun {
      strength: .025;
    }
  }
  
  &--daytime {
    background: daylight;
    .sky__clouds {
      type: fluffy;
      float: center;
    }
    .sky__sun {
      strength: .7;
      align: center;
      float: top;
    }
  }
}

BEM이 어떻게 동작하는지 좀더 깊은 이해를 하고 싶다면, 내 동료이자 친구인 Andrei Popa가 작성한 이 아티클을 살펴보기를 추천한다.

BEM은 컴포넌트를 유일하고 재사용 가능하게 만들어 준다는 점에서 유용하다. 이런 방식의 사고를 통해 기존의 오래된 스타일시트들을 이 새로운 컨벤션으로 변경하면서, 몇몇 막연하던 패턴들이 좀더 명확해졌다.

하지만, 다른 종류의 문제들이 발생하기 시작했다:

  • 클래스명 선택자가 장황해졌다
  • 이런 긴 클래스명 때문에 마크업이 불필요하게 커졌다
  • 재사용하려고 할 때마다 모든 UI 컴포넌트들 명시적으로 확장해야만 했다
  • 마크업이 불필요한 의미를 갖게 되었다

CSS 모듈과 로컬 스코프

SASS와 BEM도 고치지 못했던 몇가지 문제들은 언어 로직상에 진정한 캡슐화(encapsulation)의 개념이 없다는 것이었고, 이로 인해 개발자들이 유일한 클래스명을 선택하는 것에 의존할 수 밖에 없었다. 이런 과정은 컨벤션 보다는 도구에 의해 해결될 수 있었다.

이것이 바로 CSS 모듈이 했던 일인데, 로컬단위로 정의된 모든 스타일에 동적으로 클래스명을 만들어내어 추가된 CSS 속성들에 의해 스타일이 겹치지 않도록 해 주었으며, 모든 스타일들이 적절하게 캡슐화되도록 해 주었다.

CSS 모듈은 React 생태계에서 빠르게 인기를 얻었으며, 이제 많은 React 프로젝트에서 이를 사용하고 있는 것을 흔히 볼 수 있다. CSS 모듈 또한 나름의 장점과 단점이 있지만 전반적으로는 사용하기에 좋은 패러다임이라는 것이 증명되었다.

하지만 CSS 모듈 자체로는 CSS의 핵심 문제들을 해결하지 못했는데, 이는 단지 스타일 정의를 로컬화 할 수 있는 방법을 보여주었을 뿐이었다. 그 방법이란 바로 BEM을 자동화함으로써 더이상 클래스명을 결정하기 위해 고민할 필요가 없게 하는 (혹은 최소한 덜 고민하게 만드는) 것이었다.

CSS 모듈 역시 쉽게 확장이나 재사용이 가능하고 최소한의 노력으로 제어가 가능한 더 예측 가능한 스타일 아키텍쳐의 필요성을 완화시켜주지는 못했다.

로컬 CSS는 이렇게 생겼다.

@import '~tools/theme';

:local(.root) {
  border: 1px solid;
  font-family: inherit;
  font-size: 12px;
  color: inherit;
  background: none;
  cursor: pointer;
  display: inline-block;
  text-transform: uppercase;
  letter-spacing: 0;
  font-weight: 700;
  outline: none;
  position: relative;
  transition: all 0.3s;
  text-transform: uppercase;
  padding: 10px 20px;
  margin: 0;
  border-radius: 3px;
  text-align: center;
}


@mixin button($bg-color, $font-color) {
  background: $bg-color;
  color: $font-color;
  border-color: $font-color;

  &:focus {
    border-color: $font-color;
    background: $bg-color;
    color: $font-color;
  }

  &:hover {
    color: $font-color;
    background: lighten($bg-color, 20%);
  }

  &:active {
    background: lighten($bg-color, 30%);
    top: 2px;
  }
}

:local(.primary) {
  @include button($color-primary, $color-white)
}

:local(.secondary) {
  @include button($color-white, $color-primary)
}

일반적인 CSS와 별반 다르지 않아 보이는데, 중요한 차이점은 :local 로 시작하는 모든 클래스명들이 다음과 같은 유일한 클래스명으로 생성된다는 것이다:

.app-components-button-\_root–3vvFf {}

localIdentName 질의 파라미터를 사용하면, 생성되는 고유한 이름을 설정할 수도 있다. 예를 들어 css-loader?localIdentName=[path][name]---[local]---[hash:base64:5] 와 같이 설정하면 좀더 쉽게 디버깅할 수 있다.

이것이 바로 로컬 CSS 모듈이 동작하는 간단한 원리이다. 여기서 볼 수 있듯이 로컬 모듈들은 BEM 표기법을 자동화해주는 방법이 되었고, 유일한 클래스명을 생성해내어 심지어 같은 이름을 사용했더라도 서로가 겹치지 않게 보장해주었다. 아주 편리하다.

스타일드(Styled) 컴포넌트로 CSS를 JS와 (완전히) 섞기

스타일드 컴포넌트는 순수한 시각적 기본요소로서 실제의 HTML 태그와 맵핑될 수 있으며, 자식 컴포넌트들을 스타일드 컴포넌트로 감싸주는 역할을 한다.

다음 코드가 더 잘 설명해 줄 것이다 :

import React from "react"
import styled from "styled-components"
// Simple form component

const Input = styled.input`
  background: green
`

const FormWrapper = () => <Input placeholder="hola" />

// What this compiles to:
<input placeholder="hola" class="dxLjPX">Send</input>

스타일드 컴포넌트는 CSS 속성을 정의하기 위해 템플릿 문자열 구문을 사용하는데, 이것이 아주 간단하게 이해된다면 스타일드 컴포넌트의 핵심 개발팀이 ES6와 CSS의 능력을 잘 섞는 데에 성공했다고 생각한다.

스타일드 컴포넌트는 기능적(Functional) 혹은 상태를 가진(StateFul) 컴포넌트들로부터 UI를 완전히 분리해 내어 재사용할 수 있는 아주 단순한 패턴을 제공한다. 브라우저의 HTML이나 React Native 에서 사용될 수 있는 네이티브 태그들에 접근할 수 있는 API를 만들어내는 것이다.

커스텀 props(혹은 변경자)를 스타일드 컴포넌트에게 넘겨주는 방법은 다음과 같다.

import styled from "styled-components"

const Sky = styled.section`
  ${props => props.dusk && 'background-color: dusk' }
  ${props => props.day && 'background-color: white' }
  ${props => props.night && 'background-color: black' }
`;

// You can use it like so:
<Sky dusk />
<Sky day />
<Sky night />

props가 각각의 컴포넌트가 받을 수 있는 변경자가 되어 각기 다른 CSS를 처리하게 된 것을 볼 수 있을 것이다. 멋지지 않은가?

이렇게 함으로써 영속성과 재사용성을 보장하면서도 좀더 빠르게 JS의 모든 능력을 이용해서 스타일을 생성해 낼 수 있게 되었다.

누구나 재사용할 수 있는 핵심 UI

CSS 모듈과 스타일드 컴포넌트 모두 혼자만으로는 완벽한 해결책이 될 수 없다는 것은 금새 분명해졌다. 제대로 작동하고 확장 가능하게 만들기 위해서는 어떠한 패턴 같은 것이 필요했다. 그러한 패턴은, 컴포넌트를 로직으로부터 완전히 분리하여 스타일링 외에는 아무런 목적도 갖지 않는 핵심 컴포넌트들을 정의하는 방식으로 만들어졌다.

CSS 모듈을 사용하는 이러한 컴포넌트의 예제는 다음과 같다.

import React from "react";

import classNames from "classnames";
import styles from "./styles";

const Button = (props) => {
  const { className, children, theme, tag, ...rest } = props;
  const CustomTag = `${tag}`;
  return (
    <CustomTag { ...rest } className={ classNames(styles.root, theme, className) }>
      { children }
    </CustomTag>
  );
};

Button.theme = {
  secondary: styles.secondary,
  primary: styles.primary
};

Button.defaultProps = {
  theme: Button.theme.primary,
  tag: "button"
};

Button.displayName = Button.name;

Button.propTypes = {
  theme: React.PropTypes.string,
  tag: React.PropTypes.string,
  className: React.PropTypes.string,
  children: React.PropTypes.oneOfType([
    React.PropTypes.string,
    React.PropTypes.element,
    React.PropTypes.arrayOf(React.PropTypes.element)
  ])
};

export default Button;

예제를 보면 특별한 것은 없어 보인다. 그저 props를 받아서 자식 컴포넌트들에게 맵핑 시켜주는 컴포넌트일 뿐이다. 다른 말로 하면 : 감싸는 컴포넌트는 모든 props를 자식 컴포넌트에게 전송한다.

이제 이 컴포넌트는 다음과 같이 사용될 수 있다.

import React from "react"
import Button from "components/core/button"

const = Component = () => <Button theme={ Button.theme.secondary }>Some Button</Button>

export default Component

스타일드 컴포넌트를 사용하는 버튼을 완전히 구현한 유사한 예제를 보여주겠다.

import styled from "styled-components";

import {
  theme
} from "ui";

const { color, font, radius, transition } = theme;

export const Button = styled.button`
  background-color: ${color.ghost};
  border: none;
  appearance: none;
  user-select: none;
  border-radius: ${radius};
  color: ${color.base}
  cursor: pointer;
  display: inline-block;
  font-family: inherit;
  font-size: ${font.base};
  font-weight: bold;
  outline: none;
  position: relative;
  text-align: center;
  text-transform: uppercase;
  transition:
    transorm ${transition},
    opacity ${transition};
  white-space: nowrap;
  width: ${props => props.width ? props.width : "auto"};
  &:hover,
  &:focus {
    outline: none;
  }
  &:hover {
    color: ${color.silver};
    opacity: 0.8;
    border-bottom: 3px solid rgba(0,0,0,0.2);
  }
  &:active {
    border-bottom: 1px solid rgba(0,0,0,0.2);
    transform: translateY(2px);
    opacity: 0.95;
  }
  ${props => props.disabled && `
    background-color: ${color.ghost};
    opacity: ${0.4};
    pointer-events: none;
    cursor: not-allowed;
  `}
  ${props => props.primary && `
    background-color: ${color.primary};
    color: ${color.white};
    border-color: ${color.primary};
    &:hover,
    &:active {
      background-color: ${color.primary}; 
      color: ${color.white};
    }
  `}
  ${props => props.secondary && `
    background-color: ${color.secondary};
    color: ${color.white};
    border-color: ${color.secondary};
    &:hover,
    &:active {
      background-color: ${color.secondary}; 
      color: ${color.white};
    }
  `}
`;

이 패턴의 흥미로운 점은 컴포넌트가 dumb 이며 단지 부모 컴포넌트로 맵핑되는 CSS 정의를 감싸는 역할을 할 뿐이라는 것이다. 이 방식은 한가지 장점이 있다.

마음대로 끼워넣을 수 있는 기본 UI API를 정의하고, 모든 UI가 전체 어플리케이션 내에서 영속적임을 보장할 수 있게 해 준다.

이 방식을 통해 구현 절차로부터 디자인 절차를 완벽하게 분리해 낼 수 있으며, 원한다면 두 가지 절차를 동시에 진행할 수 있게 해 준다. 한 명의 개발자는 기능 구현에 집중하고, 다른 한 명은 UI를 다듬는 데에 집중함으로써 완전한 관심사의 분리(Separation of Concern)를 달성할 수 있다.

지금까지는 훌륭한 해결책으로 보여서, 우리는 내부적으로 논의를 거친 후에 이 패턴을 따르는 것이 좋겠다고 생각했다. 이 패턴과 함께 우리는 다른 유용한 패턴들도 만들어내기 시작했다.

Prop 수신자 (receivers)

props를 다른 컴포넌트들로 전달하는 함수를 정의함으로써, 다른 컴포넌트들이 이들 함수를 쉽게 사용할 수 있게 한다. 이를 통해 재사용성을 높이고 주어진 컴포넌트의 능력을 확장할 수 있게 된다. 변경자를 상속받는 방법이라고 생각하면 되는데, 다음 예제를 보면 의미를 알 수 있을 것이다.

// Prop passing Shorthands for Styled-components
export const borderProps = props => css`
  ${props.borderBottom && `border-bottom: ${props.borderWidth || "1px"} solid ${color.border}`};
  ${props.borderTop && `border-top: ${props.borderWidth || "1px"} solid ${color.border}`};
  ${props.borderLeft && `border-left: ${props.borderWidth || "1px"} solid ${color.border}`};
  ${props.borderRight && `border-right: ${props.borderWidth || "1px"} solid ${color.border}`};
`;

export const marginProps = props => css`
  ${props.marginBottom && `margin-bottom: ${typeof (props.marginBottom) === "string" ? props.marginBottom : "1em"}`};
  ${props.marginTop && `margin-top: ${typeof (props.marginTop) === "string" ? props.marginTop : "1em"}`};
  ${props.marginLeft && `margin-left: ${typeof (props.marginLeft) === "string" ? props.marginLeft : "1em"}`};
  ${props.marginRight && `margin-right: ${typeof (props.marginRight) === "string" ? props.marginRight : "1em"}`};
  ${props.margin && `margin: ${typeof (props.margin) === "string" ? props.margin : "1em"}`};
  ${props.marginVertical && `
    margin-top: ${typeof (props.marginVertical) === "string" ? props.marginVertical : "1em"}
    margin-bottom: ${typeof (props.marginVertical) === "string" ? props.marginVertical : "1em"}
  `};
  ${props.marginHorizontal && `
    margin-left: ${typeof (props.marginHorizontal) === "string" ? props.marginHorizontal : "1em"}
    margin-right: ${typeof (props.marginHorizontal) === "string" ? props.marginHorizontal : "1em"}
  `};
`;
// An example of how you can use it with your components

const SomeDiv = styled.div`
  ${borderProps}
  ${marginProps}
`

// This lets you pass all borderProps to the component like so:

<SomeDiv borderTop borderBottom borderLeft borderRight marginVertical>

이 방식을 사용하면 더이상 각각의 컴포넌트를 위해 모든 테두리 속성을 또다시 하드코딩 하지 않아도 되어, 많은 시간을 절약할 수 있을 것이다.

플레이스 홀더 / 유사 믹스인 기능

스타일드 컴포넌트에서는 JS를 그대로 이용해서 함수를 생성할 수 있는데, 이를 단지 props 수신자의 역할 뿐만 아니라 다른 컴포넌트들 간의 코드를 공유하기 위한 방법으로도 사용될 수 있다. 다음의 예제를 보자.

// Mixin like functionality

const textInput = props => `
  color: ${props.error ? color.white : color.base};
  background-color: ${props.error ? color.alert : color.white};
`;

export const Input = styled.input`
  ${textInput}
`;

export const Textarea = styled.textarea`
  ${textInput};
  height: ${props => props.height ? props.height : '130px'}
  resize: none;
  overflow: auto;
`;

레이아웃 컴포넌트

우리는 어플리케이션을 만들 때 가장 먼저 필요한 것이 UI 요소들의 레이아웃을 지정하는 일이라는 것을 발견했다. 이를 위해서, 우리는 레이아웃 목적의 몇가지 컴포넌트들을 만들었다.

이 컴포넌트들은 CSS로 위치를 잡는 기법에 대해 익숙하지 않아서 구조를 잡는데 어려움을 겪는 개발자들에게 매우 유용하다는 것이 증명되었다. 예제는 다음과 같다.

import styled from "styled-components";
import {
  theme,
  borderProps,
  sizeProps,
  backgroundColorProps,
  marginProps
} from "ui";

const { color, font, topbar, gutter } = theme;

export const Panel = styled.article`
  ${marginProps}
  padding: 1em;
  background: white;
  color: ${color.black};
  font-size: ${font.base};
  font-weight: 300;
  ${props => !props.noborder && `border: 1px solid ${color.border}`};
  width: ${props => props.width ? props.width : "100%"};
  ${props => borderProps(props)}
  transition: 
    transform 300ms ease-in-out,
    box-shadow 300ms ease-in-out,
    margin 300ms ease-in-out;
  box-shadow: 0 3px 3px rgba(0,0,0,0.1);
  ${props => props.dark && `
    color: ${color.white};
    background-color: ${color.black};
  `}
  &:hover {
    transform: translateY(-5px);
    box-shadow: 0 6px 3px rgba(0,0,0,0.1);
  }
`;

export const ScrollView = styled.section`
  overflow: hidden;
  font-family: ${font.family};
  -webkit-overflow-scrolling: touch;
  overflow-y: auto;
  ${props => props.horizontal && `
    white-space: nowrap;
    overflow-x: auto;
    overflow-y: hidden;
    `
  }
  ${props => sizeProps(props)}
`;

export const MainContent = styled(ScrollView)`
  position: absolute;
  top: ${props => props.topbar ? topbar.height : 0};
  right: 0;
  left: 0;
  bottom: 0;
  font-size: ${font.base};
  padding: ${gutter} 3em;
  ${props => props.bg && `
    background-color: ${props.bg};
  `}
`;

export const Slide = styled.section`
  ${backgroundColorProps}
  font-weight: 400;
  flex: 1;
  height: ${props => props.height ? props.height : "100%"};
  width: ${props => props.width ? props.width : "100%"};
  justify-content: center;
  flex-direction: column;
  align-items: center;
  text-align: center;
  display: flex;
  font-size: 3em;
  color: ${color.white};
`;

export const App = styled.div`
  *, & {
    box-sizing: border-box;
  }
`;

예제의  컴포넌트는 props로 width와 height를 받을 뿐 아니라 horizontal 을 받아서 스크롤바가 아래에 위치할 수 있게 한다.

헬퍼 컴포넌트

헬퍼 컴포넌트는 재사용성을 강화시켜 우리의 삶을 편하게 만들어준다. 모든 공통적인 패턴을 이곳에 저장하면 된다.

다음은 지금까지 내가 찾아낸 꽤 유용한 헬퍼들이다.

import styled, { css } from "styled-components";

import {
  borderProps,
  marginProps,
  backgroundColorProps,
  paddingProps,
  alignmentProps,
  positioningProps,
  sizeProps,
  spacingProps,
  theme
} from "ui";

const { screenSizes } = theme;

export const overlay = `
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0,0,0,0.5);  
`;

// You can use this like ${media.phone`width: 100%`}

export const media = Object.keys(screenSizes).reduce((accumulator, label) => {
  const acc = accumulator;
  acc[label] = (...args) => css`
    @media (max-width: ${screenSizes[label]}em) {
      ${css(...args)}
    }
  `;
  return acc;
}, {});

// Spacing

export const Padder = styled.section`
  padding: ${props => props.amount ? props.amount : "2em"};
`;

export const Spacer = styled.div`
  ${spacingProps}
`;

// Alignment

export const Center = styled.div`
  ${borderProps}
  ${marginProps}
  ${backgroundColorProps}
  ${paddingProps}
  ${alignmentProps}
  ${positioningProps}
  ${sizeProps}
  text-align: center;
  margin: 0 auto;
`;

// Positioning

export const Relative = styled.div`
  ${props => borderProps(props)};
  position: relative;
`;

export const Absolute = styled.div`
  ${props => marginProps(props)};
  ${props => alignmentProps(props)};
  ${props => borderProps(props)};
  position: absolute;
  ${props => props.right && `right: ${props.padded ? "1em" : "0"}; `}
  ${props => props.left && `left: ${props.padded ? "1em" : "0"}`};
  ${props => props.top && `top: ${props.padded ? "1em" : "0"}`};
  ${props => props.bottom && `bottom: ${props.padded ? "1em" : "0"}`};
`;

// Patterns
export const Collapsable = styled.section`
  opacity: 1;
  display: flex;
  flex-direction: column;
  ${props => props.animate && `
    transition: 
      transform 300ms linear,
      opacity 300ms ease-in,
      width 200ms ease-in,
      max-height 200ms ease-in 200ms;
    max-height: 9999px;
    transform: scale(1);
    transform-origin: 100% 100%;
    ${props.collapsed && `
      transform: scale(0);
      transition: 
        transform 300ms ease-out,
        opacity 300ms ease-out,
        width 300ms ease-out 600ms;
    `}
  `}
  ${props => props.collapsed && `
    opacity: 0;
    max-height: 0;
  `}
`;

export const Ellipsis = styled.div`
  max-width: ${props => props.maxWidth ? props.maxWidth : "100%"};
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
`;

export const Circle = styled.span`
  ${backgroundColorProps}
  display: inline-block;
  border-radius: 50%;
  padding: ${props => props.padding || '10px'};
`;

export const Hidden = styled.div`
  display: none;
`;

테마

테마는 전체 어플리케이션에서 재사용될 수 있는 값들을 한곳에서 관리할 수 있게 해 준다. 어플리케이션에서 자주 재사용되는 색상 팔레트나 일반적인 룩앤필(Look and Feel)과 같은 값들을 저장해 두는 것은 매우 유용하다.

export const theme = {
  color: {
    primary: "#47C51D",
    secondary: '#53C1DE',
    white: "#FFF",
    black: "#222",
    border: "rgba(0,0,0,0.1)",
    base: "rgba(0,0,0,0.4)",
    alert: '#FF4258',
    success: 'mediumseagreen',
    info: '#4C98E6',
    link: '#41bbe1'
  },
  icon: {
    color: "gray",
    size: "15px"
  },
  font: {
    family: `
    -apple-system,
    BlinkMacSystemFont,
    'Segoe UI',
    Roboto,
    Helvetica,
    Arial,
    sans-serif,
    'Apple Color Emoji',
    'Segoe UI Emoji',
    'Segoe UI Symbol'`,
    base: '13px',
    small: '11px',
    xsmall: '9px',
    large: '20px',
    xlarge: '30px',
    xxlarge: '50px',
  },
  headings: {
    family: 'Helvetica Neue',
  },
  gutter: '2em',
  transition: '300ms ease-in-out'
};

export default theme;

장점

  • JS의 모든 능력을 사용할 수 있다. 즉, 컴포넌트의 UI와 완전히 커뮤니케이션 할 수 있다.
  • 클래스명을 사용해 컴포넌트와 스타일을 맵핑시킬 필요가 없다 (내부에서 자동 처리된다)
  • 클래스명을 정하기 위해 고민하거나 클래스명을 컴포넌트와 맵핑시키는 시간을 절약해준다.

단점

  • 실전에서 좀더 테스트되어야 한다.
  • React 에서만 사용 가능하다
  • 이제 막 사용되기 시작했다
  • aria-label 이나 클래스명 등을 통해 테스트되어야 한다

결론

SASS, BEM, CSS 모듈, 스타일드 컴포넌트 등 어떤 기술을 사용하더라도, 다른 개발자들이 당신의 코드를 직관적으로 이해하고 많은 고민할 필요 없이 코드를 수정하여 공헌할 수 있게 하기 위해서는 잘 정의된 스타일 아키텍쳐가 가장 중요하다.

제대로 확장가능하게 만들기 위해서는 이 점이 가장 중요하며, 이는 기본 CSS든 BEM이든 어떤 것으로도 달성할 수 있다. 중요한 차이점은 각각의 구현을 위해 얼마나 많은 작업이나 코드량이 필요한가일 것이다. 스타일드 컴포넌트는 아직 더 실전 테스트가 더 필요하긴 하지만, 모든 React 프로젝트에서 훌륭한 해결책이 될 것이라 기대한다.