[자바스크립트] 세상에서 제일 쉽고 깊게 알려주는 스타일드 컴포넌트 작동원리

Date:     Updated:

카테고리:

태그:


🚀Introduction

스타일드 컴포넌트를 사용하다보면 자잘자잘한 이슈를 만나게 된다. 보통 큰 이슈는 일어나지 않는다. 그럼에도 불구하고, 기본 작동원리만 알고 있다면, 대부분의 이슈를 이해하고 쉽게 해결할 수 있다. 그렇다면, 알고보면 간단한 styled-component 작동원리와 꿀팁 몇개를 알아보도록 하겠다. 패키지설치 방법, tagged-template-literals, 기본 사용 문법 등은 알고있다고 가정하고 이야기해보도록 하겠다.


🚀 styled-component 작동순서

⭐️1. 코드작성

다음처럼 통상적인 방법으로 코드를 작성해 준다

export default function App() {
  return (
    <>
      ...
      <빨간색글자>안녕하세요</빨간색글자>
    </>
  )
}

const 빨간색글자 = styled.div`
  color: red;
  font-size: 20px;
  font-weight: bold;
  margin-top: 20px;
  text-align: center;
`;

⭐️2. 컴파일

컴파일이라고 적었지만, 내가 작성한 “빨간색글자”에 대한 규칙해석으로 보는게 더 적합할 것 같다. 내가 작성한 (코드)규칙을 해석하는 시간이다.

🏠2-1. 해시생성🏠

제일 중요한 부분이다. 내가 작성한 (CSS규칙)코드를 통해 특별한문자(해쉬값)를 생성한다. (내가 스타일 코드의 경우 iSSkAR라는 문자열이 나옴)

hash변환

🧮2-2. 규칙 계산

규칙들을 싹 긁어모아서, 최종최종최종적으로 적용될 CSS규칙을 결정한다.

⭐️3. 렌더링

React 컴포넌트가 렌더링될 때, styled 컴포넌트도 함께 렌더링된다. 우리가 익히 알고있는 리액트컴포넌트 렌더링과정과 동일하다. 다만, 컴포넌트가 해당 해쉬값을 가지고 DOM에 뿅 렌더링된다.

component

⭐️4. DOM에 추가

<style> 태그로 DOM에 추가된다. 아래 사진처럼 style태그 하위에, 특별한문자(해쉬값)로 css규칙이 잘 추가되어 있다. 지금은 한개지만, 프로젝트를 계속 만들고, 스타일을 추가해 나가다보면 저기에 수십개씩 주렁주렁 달리게 된다

style

⭐️5. 최종 렌더링

이제는 css규칙도 들어와있고, 리액트컴포넌트도 해당 해쉬값으로 class를 가지고 있게된다

(리액트컴포넌트가 들고있는 class)

이 컴포넌트에 해당 CSS가 적용된다. 해당 div는 “iSSkAR” 라는 class이고, css에는 “iSSkAR” 라는 클래스에 대한 규칙이 정의되어있으니 css가 적용된다.

final


🚀정리

오늘의 핵심이다. 간단하게 생각해보면 그냥 아래와 완전히 동일하다. 그리고 styled-component는그 해쉬값을 만들어줄 뿐인 것이다. 그냥 중복 안되게 가운데서 해쉬값으로 이름붙여주는 역할을 수행해 주는 것이였다. 알고보면 별게 없다.

html-css


그럼…


🚀원리를 알면 보이는 이슈(+Advanced)

원리를 알고나면 자주 발생하는 아래 이슈들도 쉽게 이해가 될것이다.

🚨이슈1: 200개 이슈

가끔 스타일드 컴포넌트를 사용하다보면 이런 경고창이뜬다. warning

“Over 200 classes were generated for component styled.div with the id of “sc-beySPh”. Consider using the attrs method, together with a style object for frequently changed styles”

왜그럴까? 작동원리를 알고 있으니 쉽게 생각해볼수 있다. 다른 스타일에 따라 계속 다른 해쉬값이 생성되기 때문이다. 슬라이더에 따라 변하는 아래코드는 다음과 같이 미친듯이 많은 클래스를 만들어 버린다.

<>
  ...
  <빨간색글자 fontSize={fontSize}>안녕하세요</빨간색글자>
</>


const 빨간색글자 = styled.div`
    color: red;
    font-size: ${props => props.fontSize}px;
    font-weight: bold;
    margin-top: 20px;
    text-align: center;
`

그 결과 아래 그림처럼 수많은 해쉬값이 생성이 된다. 바뀌는 값들의 모든버전에 대해서 모든버전의 해쉬값(스타일규칙)을 생성하기 때문이다.

so-many-font-size

실제 개발자도구를 열어보면, 슬라이더로 새로운 폰트사이즈를 줄 때 마다 다음처럼 수많은 해쉬값(스타일 규칙들)이 생성되어버린다

so-many-style

그 결과 다음과 같은 경고창이 뜬다.

“Over 200 classes were generated for component styled.div with the id of “sc-beySPh”. Consider using the attrs method, together with a style object for frequently changed styles”

warning

어떻게 해결할 수 있을까? 경고창에서 알려주는대로 attr 메소드를 사용하면 된다. 이게 어떤 역할을 하는 걸까?

⭐️해결방법 1: 인라인으로 해결


<>
  ...
  <빨간색글자 style={{fontSize: `${fontSize}px`}}>안녕하세요</빨간색글자>
</>



const 빨간색글자 = styled.div`
    color: red;
    font-size: 20px;  //실제로는 인라인스타일로만 동작
    font-weight: bold;
    margin-top: 20px;
    text-align: center;
`

마치 이렇게 인라인 스타일을 적용하는것과 완전히 동일하게 작동한다. 실제로 런타임에서는 슬라이더의 값이 인라인속성으로들어온 fontSize가 적용된다. 개발자도구를 통해서, 아래처럼 인라인스타일이 적용되고, styled-components에서 작성한 규칙은 무시되는걸 볼 수 있다.

inline-style

⭐️해결방법 2: attrs 메서드로 해결

아래 경고창에서 알려주는것처럼 attrs 메서드를 똑같이 사용해 보겠다

warning

<>
  ...
  <빨간색글자 mySize={fontSize}>안녕하세요</빨간색글자>
</>


const 빨간색글자 = styled.div.attrs(props => ({
  style: { fontSize: `${props.mySize}px` }
}))`
    color: red;
    font-size: 20px;
    font-weight: bold;
    margin-top: 20px;
    text-align: center;
`

이렇게 작성하면 해결방법 1처럼 완벽하게 해결이 가능하다. 1번과 2번 모두 문제를 해결한다. 하지만 이렇게 해결하면 또 다른 이슈를 만들어낸다(조금 더 읽으면 나옴) 공식문서 에서는 꼭 attrs를 써야 하는 상황은 다음처럼 알려주고 있다.

<input type="text" placeholder="바보아니다" />

위와 같이 “스타일 속성이 아닌 html 기본속성들”을 attrs를 이용해 쉽게 초기화할 수 있는 것이다.

//attrs를 이용해서 기본속성들 재활용 ( 코드가 더 간단해보임 )
<>
 <입력하는곳/>
 <입력하는곳/>
 <입력하는곳/>
</>


const 입력하는곳 = styled.input.attrs({
  type: 'text',
  placeholder: '바보아니다',
})`
  //스타일 정의
  padding: 8px;
  margin: 10px 0;
  border: 1px solid #ccc;
  border-radius: 4px;
`;

attrs가 없었다면 입력하는곳이라는 컴포넌트를 만들때마다, typeplace홀더 등등을 반복해서 적어줘야하기 때문이다.

//attrs가 없어서 컴포넌트를 만들 때 고생해야함 ( 코드작성하기 더 귀찮아지고, 실수할수도 있음 )
<>
 <입력하는곳 type="text" placeholder="바보아니다" />
 <입력하는곳 type="text" placeholder="바보아니다" />
 <입력하는곳 type="text" placeholder="바보아니다" />
</>


const 입력하는곳 = styled.input.attrs({
  type: 'text',
  placeholder: '바보아니다',
})`
  //스타일 정의
  padding: 8px;
  margin: 10px 0;
  border: 1px solid #ccc;
  border-radius: 4px;
`;

정리하자면,

attrs는 html의 (스타일속성이 아닌)기본속성들을 (마치 인라인으로 적은것처럼)정의할때 사용할 수 있으며, prop이 너무 자주바뀌어서 많은 스타일 클래스를 만들어 내는 문제의 해결책이 될 수 있다. 마치 컴포넌트에 인라인으로 적은것처럼 작동하기 때문이다.

그렇다. 200개의 class가 넘어갈때 생기는 오류는, 인라인스타일로 해결할수 있으며, 마치 인라인으로 적어둔것처럼 도와주는 attrs메서드를 통해 해결할 수 있다. 둘중 뭘로 해결할지는 상관이 없지만, attrs를 이용해서 스타일드 컴포넌트에서 모든 속성을 한번에 관리하는게 실수할 위험을 줄일 수 있지 않을까 싶다. 사실 너무 마이너한 고민이라, 둘중 아무거나 하나로 처리하고 넘어가면 될 것 같다😎


🚨이슈2: 없는속성 이슈

위의(해결방법2) 코드를 돌리면 경고가 뜬다.

<>
  ...
  <빨간색글자 mySize={fontSize}>안녕하세요</빨간색글자>
  ...
</>


const 빨간색글자 = styled.div.attrs(props => ({
  style: { fontSize: `${props.mySize}px` }
}))`
    color: red;
    font-size: 20px;
    font-weight: bold;
    margin-top: 20px;
    text-align: center;
`

transient-props

왜 위와같은 경고가 뜨는걸까? 경고창에 있는 것 처럼 DOM에서는 mySize라는 속성이 뭔지 모르기 때문이다.

mysize

이럴때는 간단하게 다음처럼 딸러사인을 추가해주면 된다. 공식문서에서는 transient-props라고 불린다. 아래처럼 처리해주면, 더이상 DOM에 mySize는 보이지 않고, 이슈가 해결된다.

</>
  ...
  <빨간색글자 $mySize={fontSize}>안녕하세요</빨간색글자>
</>

const 빨간색글자 = styled.div.attrs(props => ({
  style: { fontSize: `${props.$mySize}px` }
}))`
    color: red;
    font-size: 20px;
    font-weight: bold;
    margin-top: 20px;
    text-align: center;
`


🚨이슈3: 속도이슈

사실 위에서 이야기했던 이야기들 모두 dev환경에서 스타일드 컴포넌트를 사용할때의 이야기이다. 사실 위에서 이야기했던 경고들도 production환경에서는 뜨지 않는다. 왜냐하면, production환경에서는 style태그를 통해서 CSS를 추가하지 않기 떄문이다.

dev환경에서는 node.appendChild()를 사용해서 CSS를 추가하고

production환경에서는 CSSStyleSheet.insertRule()를 사용하여 CSS를 추가한다.

production환경에서처럼 insertRule()방식으로 CSS를 집어넣는게 10배, 20배 더 빠르다고 한다. 그렇다면 10배나 더 빠른데, 사용자경험이 훨씬 좋을텐데, 왜 dev환경에서는 굳이굳이 appendChild()방식으로 CSS를 추가해줄까?

조금 느리더라도, DOM에 직접 기록되어있기때문에 개발자가 쉽게 이리저리 만져보고 확인해볼 수 있기 떄문이다. 어떤 규칙들이 적용되어있는지 개발자도구 등에서 (DOM에 기록되어있기때문에)쉽게 확인할수 있기 때문이다.

append-insert

무튼, production환경에서는, 위에서 봤던것과 같이, 많은 class를 만들어내거나 하는 방식이 아니라 직접적으로 CSSOM에 스타일 규칙을 추가시켜주는 방식으로 작동하고, 훨씬 빠르게 작동한다.


🚨이슈4: 디버깅 이슈

이슈라기보다는, 꿀팁이다. babel-plugin-styled-components라는 바벨 플러그인 설치해주면 디버깅이 훨씬 쉬워진다

필자의 경우 vite.config.ts에 다음과 같이 추가(수정)해주었다.

//vite.config.ts 파일
plugins: [
    react({
      babel: {
        plugins: [
          [
            "babel-plugin-styled-components",
            {
              displayName: process.env.NODE_ENV !== "production",
              fileName: false,
            },
          ],
        ],
      },
    }),
  ],

위와같은 방버법으로 production일때는 굳이 컴포넌트명을 보이게하진 않았다. displayName: import.meta.env.DEV 으로 하거나, 파일이름 노출 등 본인이 입맛대로 사용해보면 되겠지만, production 에서 굳이 불필요한 정보들은 들어가지 않도록 처리했다. 그 결과, 개발환경에서는 다음처럼 컴포넌트 이름이 잘 보인다. 문제가 있어보이는 컴포넌트를 찾아갈때, 훨씬 빠르게 디버깅이 가능하다. 해당 플러그인은 공식문서에서도 당당히 소개되어있는걸 보니, 위 링크에서 사용법을 한번 보고, 믿고쓰면 될것 같다.

baebl-plugin


🚀마무리

다음과 같은 결론들이 난다.

  1. styled-components는 작성된 코드텍스트를 해쉬값으로 만들어준다.⭐️⭐️⭐️
  2. 해당 해쉬값을 가진 class를 통해 컴포넌트를 스타일링한다.
  3. 속성에따라 스타일이 많이바뀌면 class가 많이 만들어진다. 그리고 경고가 뜬다.
  4. production 환경에서는 class가 많이 만들어지지는 않는다.
  5. 경고를 해결하기 위해서는 인라인 스타일로 주거나, attrs를 주면 된다.
  6. production 환경에서는 10배, 20배 빠르다.
  7. babel-plugin을 통해서 styled-components를 훨씬 빠르게 디버깅할 수 있다.

결국 내가 작성한 코드를 해슁하고, (dev환경에서는) 해당 클래스명을 통해 스타일을 처리해주는 그 작동원리만 이해하면, 대부분의 이슈는 쉽게 처리할 수 있을 것 같다.


맨 위로 이동하기

javascript 카테고리 내 다른 글 보러가기

댓글 남기기