TIL 23. 11. 07

오늘 한 일

  • CSS의 역사
  • CSS in JS
  • Styled Components
    • 동작 원리
    • 아이디어

들어가며

React 숙련주차를 시작하며 CSS-in-JS 라이브러리인 styled-components를 학습했다. 보다 깊은 이해를 위해 styled-components의 동작 원리를 알아보다가 CSS의 발전 과정에서 어떻게 styled-components가 등장하게 되었는지 정리해보았다.

CSS의 역사

CSS 역사로 알아보는 CSS가 어려워진 이유를 참고해 styled-components가 탄생한 배경까지 요약.

  • 1990, 웹에서 문서를 공유하기 위해 HTML 언어가 만들어짐

    • inline 방식으로 스타일을 적용
    • 요소마다 개별적으로 스타일을 적용하였기에 HTML 코드가 복잡해지고 반복해서 작성을 해야했음
  • 1996, HTML에서 스타일만 분리한 CSS

    • inline-style이 기록된 부분들을 하나로 묶어 별도의 스타일을 선언
    • HTML에서 원하는 태그를 찾아서 선택(selector) 하여 스타일을 적용
    • 같은 요소가 여러 선언의 대상이 된 경우, 어떤 선언의 CSS 속성을 적용할 것인가? ➡ CSS에 우선순위가 부여 (CSS Cascade)
    • 기존의 CSS를 덮어쓰기 위해 더 복잡한 Seletor를 써야하는 원인
  • 단순 문서에서 게시판과 같은 웹 서비스로 발전

    • 사용자 입력을 받아 동적으로 처리하기 위해 서버에서 HTML을 작성해서 응답
    • HTML 구조를 그대로 두고 CSS만으로 디자인을 해야하는 요구사항 발생
  • Selector가 점점 더 복잡해짐

    • HTML을 수정하지 않고 원하는 디자인을 만들기 위해 정교하고 고도화된 Selector가 필요하게 됨
  • 2006, Sass의 등장

    • CSS를 확장하여 사용하기 위해 추가적인 문법(네스팅, 변수)으로 작성하면 CSS로 변환시켜주는 pre-processor
  • HTML5, CSS3 - 의미에 중점을 둔 시맨틱이 중요해짐

  • 2008, jQuery, Ajax 보편화

    • 프론트엔드에서 HTML과 CSS를 함께 작성하고 서버로부터 응답받아 JavaScript로 처리
    • 프론트엔드에서 HTML, CSS를 모두 다루기 때문에 복잡한 Selector를 사용하지 않고 HTML에 class를 추가하는 스타일링 방식으로 발전
  • 2013, 웹 애플리케이션과 Framework

    • React를 비롯한 Web Framework가 만들어지면서 HTML 작성하는 주체가 프론트엔드의 영역이 되기 시작
    • 페이지 단위가 아닌 컴포넌트 기반의 개발 방식이 자리 잡음
    • 그러나, CSS의 문제점인 전역성과 Cascade 특성으로 인해 다른 컴포넌트 영역의 스타일을 수정하기도 함
  • 2013, CSS 방법론과 BEM

    • CSS를 잘 작성하는, 체계적으로 규칙을 가지고 작성하려는 방법론 등장
    • 크고 복잡한 프로젝트에서 팀 구성원 간 쉬운 의사소통을 위해, Cascade 특성을 관리하기 위한 명명 규칙 - BEM
  • 2013, Flexbox 등장

    • 레이아웃을 위한 CSS
    • CSS 스펙으로 웹 애플리케이션 레이아웃을 할 수 있는 토대
  • 2014, Zeplin - 디자이너를 위한 핸드오프툴

    • 기존에는 디자이너가 웹 디자인과 퍼블리싱 동시에 진행
    • 핸드오프툴 등장 이후 디자이너와 퍼블리싱의 영역이 분리
  • 2015, IE11 (비공식) 업데이트 중단

    • IE는 새로운 CSS 스펙이 나와도 지원할 수 없어 적극적으로 사용할 수 없었음
    • IE11 업데이트 중단 이후로 CSS의 문제점을 JS로 해결하려는 시도 증가
  • 2015, CSS Modules

    • CSS 클래스를 불러와 사용 할 때 클래스명을 고유한 이름으로 자동 변환해줌으로써 CSS 클래스명이 서로 중첩되는 현상을 미연에 방지해 주는 기술
    • Global Scope에 대한 해결
  • 2016, CSS-in-JS: Styled-Components

CSS-in-JS

CSS-in-JS는 자바스크립트로 CSS를 작성하는 방식으로 CSS를 문서 레벨이 아닌 컴포넌트 레벨로 적용한다. 현대 웹은 페이지가 아니라 컴포넌트 단위로 만들어 조합하여 웹 페이지를 구성한다. 자바스크립트를 활용하며 컴포넌트 레벨로 추상화하기에 여러 장점이 있다.

  • 지역 스코프 스타일: 전역으로 관리되는 CSS는 중복 적용되어 실수하기 쉽다. 지역 범위로 CSS를 적용하여 이러한 문제를 해결

  • 코로케이션(colocation): 관련된 코드들을 같은 위치에 포함

    • 코로케이션(colocation)은 단일 위치 내에 여러 엔티티를 배치하는 행위이다.

  • 변수 사용: JavaScript와 CSS 간 상태나 함수 공유가 가능

  • 컴포넌트 단위로 분리: HTML, CSS, JavaScript와 같은 커다란 단위의 목적을 기준으로 분리하는 것이 아닌 스타일을 포함한 컴포넌트 단위로 분리 가능

  • 사용하지 않는 스타일 제거: JavaScript를 사용하기 때문에 사용하지 않는 코드를 제거할 수 있다. CSS는 사용되지 않는 코드를 검출하지 못함

Styled-components

styled-components는 컴포넌트에 스타일을 효율적으로 작성하기 위한 대표적인 CSS-in-JS 라이브러리 중 하나이다. 애니메이션, 테마, 전역 스타일링, props 기반의 동적 스타일링 등 여러 강력한 기능들을 제공한다.

styled-components는 태그가 지정된 템플릿 리터럴(Tagged Template Literals)이라는 ES6 문법을 사용하여 스타일을 지정한다. 백틱(`) 기호 내에 CSS 코드를 작성하면 스타일이 적용된 React 컴포넌트를 반환한다.

Styled-components 동작 원리

Tagged template literals이란?

template literals의 더욱 발전된 한 형태는 tagged templates 입니다. 태그를 사용하면 템플릿 리터럴을 함수로 파싱 할 수 있습니다. 태그 함수의 첫 번째 인수는 문자열 값의 배열을 포함합니다. 나머지 인수는 표현식과 관련됩니다. - Template literals - MDN

잘 와닿지 않는다. 사용해보면서 알아보자.

  const logArgs = (...args) => console.log(...args);
  const favoriteFood = 'Pizza';

  logArgs(`I like ${favoriteFood}`); // 'I like Pizza'
  logArgs`I like ${favoriteFood}`; // ['I like ', ''], 'Pizza'

logArgs`` 문법은 logArgs이라는 함수를 호출하는 것 logArgs()와 같다. 하지만 차이점은 tageed templates literals를 사용하면 템플릿 리터럴이 모두 분할되어 문자열 배열을 첫 번째 인자로 가지고 다음 인자는 스트링 템플릿(${})으로 전달된 값들을 갖는다.

const execFuncArgs = (...args) => args.forEach(arg => {
  if (typeof arg === 'function') {
    arg()
  }
})

execFuncArgs(`Hi, ${() => { console.log('Executed!') }}`)
// undefined

execFuncArgs`Hi, ${() => { console.log('Executed!') }}`
// "Executed!"

tagged templates literals로 함수가 호출될 때 실제 함수가 전달된다. 위의 예제에서 괄호를 사용해 execFunArgs 함수를 호출했을 때는 실제 함수가 전달되지 않았기 때문에 아무 일도 일어나지 않는다. 그저 단순한 문자열이 전달된 것이기 때문.

template literal 문법으로 함수를 전달할 수 있기 때문에 props에 기반한 동적인 스타일링이 가능하다.

아이디어

Demystifying styled-components 글을 참고해 작성했다.

function h1(styles) {
  return function NewComponent(props) {
    const uniqueClassName = comeUpWithUniqueName(styles);
    const processedStyles = runStylesThroughStylis(styles);
      
    createAndInjectCSSClass(uniqueClassName, processedStyles);
      
    return <h1 className={uniqueClassName} {...props} />
  }
}
  • Tagged template로 전달된 css string styles를 평가한다

  • styles를 해시 알고리즘을 사용하여 dKamQW처럼 유니크한 클래스명을 생성한다

  • lightweight CSS preprocessor Stylis를 통해 CSS를 실행한다. 유효한 CSS를 만드는 작업

    • {
          font-size: 1.5em;
          text-align: center;
          color: palevioletred;
      }
      
  • 생성한 클래스명을 이름으로 사용하고 styles 문자열의 모든 CSS 선언을 포함한 새로운 CSS 클래스를 페이지에 주입한다.

    • <style>
          .dKamQW {
              font-size: 1.5em;
              text-align: center;
              color: palevioletred;
          }
      </style>
      
  • 반환된 HTML Element에 classname을 적용한다.

    • <style>
        .dKamQW {
          font-size: 1.5em;
          text-align: center;
          color: palevioletred;
        }
      </style>
      <h1 class="dKamQW">Hello World</h1>
      

확인해보기

import React from 'react';
import styled from 'styled-components';

export default function App() {
  const H1 = styled.h1`
    font-size: 1.5em;
    text-align: center;
    color: palevioletred;
  `;

  console.log(H1);
  return <H1>Title</H1>;
}

first

second

결론

styled-components는 렌더링 될 때 DOM 요소에 고유한 클래스명이 주입되므로 다른 요소에 영향을 미치지 않으며 CSS 표준 문법을 사용해 쉽게 스타일을 적용할 수 있다. 또한, 기존의 classname에 따른 스타일링이 아닌 컴포넌트의 속성(props)에 따라 동적으로 스타일링할 수 있도록 해준다.

느낀점

CSS 역사를 살펴볼 수 있는 좋은 기회였다. 기술이 등장하게 된 배경을 알아보면 어떤 단점을 해결하기 위해 등장한 기술인지 알게 된다. CSS는 항상 문제다.

그런데 npm trends를 살펴보면 최근 CSS-in-JS 라이브러리 사용량이 줄어들고 있다. 이유는 아직 찾지 못했다. 나 역시 CSS-in-JS를 사용하기보다 css module을 더 많이 사용하긴 했다. 시맨틱한 구조를 파악하기 위해 스타일된 컴포넌트보다 HTML 태그로 개발하는 것이 편했고 성능 이슈가 있었기 때문에.

하지만 동적으로 스타일링할 수 있다는 것은 분명한 장점이다. className을 변경하지 않아도 props로 전달하여 쉽게 스타일을 바꿀 수 있었다. 이번 과제에서는 styled-components의 다양한 기능들을 사용해보며 기술 스택을 늘려봐야겠다.

Reference

카테고리:

업데이트:

댓글남기기