본문 바로가기
React

styled-component API를 살펴보고 구현해보기

by Luke K 2024. 3. 10.

현재 프로젝트에서 css 스타일링 방식으로 styled-component를 사용하고 있습니다.
사용하며 백틱을 사용한 문법으로 함수를 만들려면 어떻게 해야할까?
실제로 어떤 과정으로 스타일이 생성될까? 에 대해 정리하며 이해해보고 싶었습니다.

그런 이유로 styled-component 의 원리와 사용할 때 볼 수 있는 편의성들에 대해 정리해보려합니다.
또 간단하게 styled-component를 만들어보는 글을 작성하려합니다.


TOC

  1. styled-component는 무엇을 해주는 라이브러리인가?
  2. styled-component 에서 제공하는 API
  3. styled-component 의 동작원리
  4. 간단하게 만들어보는 styled-component

styled-component는 무엇을 해주는 라이브러리인가?

styled-component 공식문서에서 한줄 소개를 다음과 같이 합니다.

Visual primitives for the component age. Use the best bits of ES6 and CSS to style your apps without stress 💅

 

즉 styled-component 는

  • 컴포넌트 시대를 위한 시각적 구성요소입니다.
  • 또 스트레스 없이 es6와 css 문법을 사용해서 스트레스 없이 앱을 스타일링할 수 도와주는 라이브러리라고 합니다.

이 문장을 통해 다음과 같은 내용을 유추할 수 있었습니다.

  • styled-component 는 컴포넌트 단위로 스타일을 제작하는 API를 제공할 것 같습니다.
    또 ES6 문법과 CSS 문법을 사용하여 스타일링을 할 수 있을 것 같습니다.
  • 스타일링 스트레스를 줄여주기 위한 편의성을 제공할 것 같습니다.

그럼 본격적으로 어떠한 형태의 API를 제공하길래 다음과 같은 내용을 할 수 있는 지 살펴보겠습니다.

 

 

styled-component 에서 제공하는 API

공식문서에서 제공하는 예제 먼저 살펴봅시다.

const Button = styled.button`
  color: grey;
`;

styled객체안에 있는 button이 함수인가봅니다.
css 코드를 매개변수로 받고 컴포넌트를 리턴하는 형식 같네요.

여기서 특이한 점이 있습니다.

제가 아는 함수는 button('color: grey') 꼴이어야할 것 같은데 소괄호가 생략돼있네요.

어떻게 그럴 수 있을까요?

Tagged Template literals

이 문법은 함수가 문자열을 받을 때 소괄호 대신 템플릿 리터럴 문법을 사용할 수 있게 만들어줍니다.

이덕에 우리는 소괄호안에 백틱을 감쌀 수고를 덜게 됐네요.

여기서 styled-component의 Use the best bits of ES6 라는 말을 이해할 수 있게 됩니다.

왜냐면 바로 이 Tagged Template literals 가 바로 ES6 문법이기 때문입니다.

 

그러면 Use the best bits of ES6 and CSS 에서 CSS의 좋은 사용 방법으로는 무엇을 제공해줄까요?

공식문서에 써져있는 내용과 예제 코드를 살펴봅시다.

 

Pseudoelements, pseudoselectors, and nesting

import styled from 'styled-components';

const StyledComponent = styled.div`
  color: blue;
  & {
    background-color: yellow;
  }
  && {
    font-size: 20px;
  }
  & span {
    color: green;
  }
`;

가상요소, 가상선택자, nesting을 지원한다는말인데요.

즉 css3 문법인 가상요소, 가상 선택자와 scss 문법인 nesting을 지원합니다.

이외에도 styled-component를 사용하게 되면 js 문자열로 스타일링하게 되지만 sass 처리 기반의 문법은 기본적으로 지원하고 있습니다.

확실히 sass 관련 환경설정 없이 sass 문법까지 쓸 수 있는 강력한 부분이 있어보입니다.

하지만 이게 런타임에 일어나는 작업이라면 렌더링 속도에 영향을 미치진 않을까라는 의심이 들기도 합니다.

그럼 이제 styled-component가 실제로 어떻게 동작할 지 살펴볼까요?

 

styled-component 동작원리

styled-component 가 만들어지는 시작점이었던 styled.[html태그] api를 다시 살펴보면 좋을 것 같습니다.

const Button = styled.button`
  color: grey;
`;

button 메서드를 사용하고 인자에 css 문자열을 넣으면 Button이라는 컴포넌트가 리턴됩니다.

그리고 만들어진 컴포넌트는 다른 React Component 처럼 사용할 수 있습니다.

const App = () => {
  return (
    <div>
      <Button>Click Me</Button>
    </div>
  );
};

 

styled.메서드로 만들어진 컴포넌트의 실행에서의 실제 동작과정은 다음과 같습니다.

styled-component 코드에서 참고할 수 있었습니다. [코드 바로가기]

  1. 컴포넌트 정보 추출: 속성, 컴포넌트 스타일, 기본 속성을 추출합니다.
  2. 테마 결정: styled-component가 제공하는 ThemeProvider에서 테마를 가져옵니다.
  3. 속성과 컨텍스트 해석: 컴포넌트의 속성(attrs)와 사용자로부터 전달된 props, 결정된 테마를 통해 최종 컨텍스트를 해석합니다.
  4. 생성할 엘리먼트 종류 결정: context.as 또는 target을 기반으로 실제 생성할 DOM 요소의 종류를 결정합니다.
  5. 요소에 전달할 속성 정제: 컨텍스트에서 가져온 속성을 반복하여 처리하고, 전달할 속성(propsForElement)을 준비합니다. 여기서는 정의된 규칙에 따라 일부 속성을 생략하거나 변경합니다.
  6. 스타일 적용: useInjectedStyle 훅을 사용하여 컴포넌트 스타일을 적용하고, 생성된 클래스 이름을 받습니다.
  7. 클래스 문자열 생성: 컴포넌트 ID와 생성된 클래스 이름을 조합하여 최종 클래스 문자열을 만듭니다.
  8. 클래스 속성 설정: 최종 클래스 문자열을 propsForElement에 className 또는 class로 설정합니다. 이는 DOM 요소의 종류에 따라 달라질 수 있습니다.
  9. 참조(ref) 설정: forwardedRef를 propsForElement에 ref로 설정합니다.
  10. DOM 요소 생성: React.createElement를 사용하여 결정된 요소와 속성을 기반으로 실제 DOM 요소를 생성합니다.

하지만 이렇게 보면 너무 어렵습니다.
이 10가지 순서를 머리속에 인출하기 좋은 형태로 다 들고 다니는 것은 무리인 것 같습니다.
쉽게 정리하기 위해 attrs, as 등의 API는 제외하고 생각해봅시다.

 

  1. 컴포넌트 정보 추출: 속성, 컴포넌트 스타일, 기본 속성을 추출합니다.
  2. 전역에 있는 스타일 가져오기: styled-component가 제공하는 ThemeProvider에서 테마를 가져옵니다.
  3. 클래스 문자열 및 컴포넌트 스타일 생성: styled-component 전용 className들과, 그 className에 매칭되는 스타일을 생성합니다.
  4. DOM 요소 생성: React.createElement를 사용하여 결정된 요소와 속성을 기반으로 실제 DOM 요소를 생성합니다.

그럼 위 내용들을 바탕으로 간단하게 styled-component 비슷하게 만들어보겠습니다.

간단하게 만들어보는 styled-component

구현해보는 이유는 다음과 같습니다.

  • 비슷한 형태의 API 를 만들 때 활용하기 위해
  • styled-component에 대해 조금 더 깊은 이해를 하기 위해 인출하는 연습을 하려고 합니다.

 

간단하게 다음과 같은 순서로 만들어보려합니다.

  1. styled 객체를 만들어 안에 태그이름에 맞는 메서드를 집어넣는다.
  2. 각각의 메서드는 태그드 템플릿 리터럴로 css 문자열을 받는다.
  3. 겹치지 않을만한 className을 만들고 이 className에과 스타일을 매치하여 head 태그에 주입한다.
  4. 3.에서 만든 className을 넣은 React Element를 생성하여 반환한다.

 

import React from 'react';

// 클래스 이름 hash를 이용하여 만들기
const generateHash = (str) => {
    let hash = 0;
    for (let i = 0; i < str.length; i++) {
        const char = str.charCodeAt(i);
        hash = (hash << 5) - hash + char;
        hash |= 0; 
    }
    return `styled-${hash}`;
};

// head 태그에 만들어진 style 주입하기
const insertStyle = (cssText) => {
    const style = document.createElement('style');
    style.type = 'text/css';
    style.appendChild(document.createTextNode(cssText));
    document.head.appendChild(style);
};

const tags = ['div', 'button', 'p', 'span'];
const styled = {};

tags.forEach(tag => {
    // Tagged Literal Templates 사용할 수 있는 형태로 styled 객체에 메서드 만들기
    styled[tag] = (strings, ...values) => {
        const styleString = strings.reduce((acc, str, i) => {
            return `${acc}${str}${values[i] || ''}`;
        }, '');

        const className = generateHash(styleString);
        const cssText = `.${className} { ${styleString} }`;
        insertStyle(cssText);

        return ({ children, ...props }) => {
            return React.createElement(tag, {
                ...props,
                className: `${props.className ? `${props.className} ` : ''}${className}`
            }, children);
        };
    };
});

export default styled;

 

마무리

이렇게 styled-component 제작자들이 styled-component를 소개하는 방식을 살펴보았습니다.

또 styled-component 에서 제공하는 API 의 특성과 동작원리, 간단한 구현을 통해 살펴보았습니다.
제가 만든 styled-component는 글을 쓰며 읽은 실제 코드와는 다른 부분이 많습니다.

여러 as, attrs 같은 API나 런타임(웹 혹은 네이티브), ThemeProvider를 고려하지 않고 제작했습니다.
이 부분에 대한 실제 코드에서 배운 점이 많아, 이 부분도 정리해보면 좋을 것 같습니다.

긴 글 읽어주셔 감사합니다.

댓글