[Error] styled-component 사용시 주의점 (css 관련)

devAnderson·2022년 6월 1일
0

Error Handling

목록 보기
4/12

0. 쓰는 이유

최근 컴포넌트의 재사용화와 일반화를 위해서 방법을 찾다가 여러가지를 시도해보고 있었는데, 그 중에 styled-component로 인해 발생한 스타일 에러와 규칙을 하나 알게 된 게 있어서 글을 남긴다. 상당히 당황스럽지만, css의 세계는 정말 알다가도 모르겠다.

1. 🚍 styled vs class

내가 시도하고 있던 내용은, props로 "css" 라는 스타일링 데이터를 리턴하는 스타일드 컴포넌트의 함수와, class를 이용한 디자인을 병합하여 조건부 컴포넌트를 만드려고 하고 있었다.

개인적으론 클래스가 CSSOM 속도도 더 빠르고, 유지보수에 있어서도 해시값된 클래스만이 아닌 구분자로서 사용할 클래스가 생겨난다는 점에서 의의가 있으리라고 생각했으나, 디자인 적용에서 서로간에 우선순위가 존재할줄은 꿈에도 생각하지 못했다.

스타일드 컴포넌트의 템플릿 리터럴 내 css 내용을 보면 클래스에 따라서 디자인이 달라지고, 마지막으로 additionals 라는 프로퍼티로 전달되는 스타일드 컴포넌트의 css 메서드 배열을 통해 추가적인 디자인이 적용되도록 진행하였다.

하지만, 정말 슬프게도 위의 방법을 사용하면 이상하게도 background만 제대로 오버라이드가 안되는 현상이 발생했다.

2. 🚍 template literal

그래서 나는 css 메서드의 호출결과를 저렇게 배열로 넣어두는 일이 문제였나 싶어서 template literal이 정확하게 어떻게 동작하는지 원리를 알고 싶어졌다.

template literal 은, 말 그래도 함수 호출을 하는데 인자를 리터럴 스크링 형태로 보내줘서 호출하는 방식을 뜻한다.

이때 호출을 하게 되면 다음과 같은 현상이 벌어진다.

  1. 함수 인자로 리터럴 내의 스트링이 들어온다
  2. 스트링들을 ${} 기호를 만날 때마다 쪼갠다
  3. ${} 내부의 내용은 따로 모아져셔 raw라는 프로퍼티에게 배열로서 전달한다.
  4. 해당 인자를 가지고 함수를 실행한다.

이것을 사진으로 보면 다음과 같다. (출처는 벨로퍼트님)

따라서, 이런 템플릿 리터럴 특성을 활용하여 특정한 문자열의 전환이 가능해진다.

const red = '빨간색';
const blue = '파란색';
function favoriteColors(texts, ...values) {
   return texts.reduce((result, text, i) => `${result}${text}${values[i] ? `<b>${values[i]}</b>` : ''}`, '');
}
favoriteColors`제가 좋아하는 색은 ${red}${blue}입니다.`
// 결과 : 제가 좋아하는 색은 <b>빨간색</b>과 <b>파란색</b>입니다.

styled-component는 네임스페이스로 생각해줄때, 해당 네임스페이스에 미리 정해져 있는 태그들의 이름을 가진 키들은 다 템플릿 리터럴을 처리하는 함수로 구성되어 있다.

import React, { useRef, useEffect } from 'react'
import domElements from './domElements'

const myStyled = TargetComponent => ([style]) => props => {
  ...
  (위와 동일)
}

// NEW!
domElements.forEach(domElement => {
  myStyled[domElement] = myStyled(domElement)
})

export default myStyled

// index.js
const Button = myStyled.button`
  color: red;
  border: 2px solid red;
  border-radius: 3px;
`

ReactDOM.render(<Button>Click me</Button>, rootElement)

구조를 보면 좀 특이하다는 것을 알 수 있다. 우리가 자주 쓰는 styled component의 default값은 사실 함수다. 근데 웃기게도 함수가 객체라는 점을 이용해서 본인 스스로를 호출하면서 함수 객체에다가 태그 이름별로 정적 메서드를 삽입한 함수다.

그래서 그 함수 자체가 네임스페이스적으로 사용이 가능하다.

결국 우리가 자주 쓰는 styled.div``의 의미는,

렉시컬 환경에 html element와, style에 대한 템플릿 리터럴의 배열을 가지고 있는 클로져 라는 뜻이 된다.

그래서 컴포넌트에 어트리뷰트로 props를 전달하고 있는 것이었다.

여튼, 결과적으로 말하자면 함수의 호출을 한다는 것인데, 이제 중요한 부분은 style 배열 부분이다.

위에서 언급한 것처럼, 템플릿 리터럴을 사용할 경우 결과는 사실상 분리된 스트링값의 배열과 ${} 내용물이 들어간 배열이 합쳐진 유사배열로 보인다.

그리고 이 유사 배열이 전송이 되었을 때 ${} 표시를 어떻게 처리하는지에 대한 과정이 중요한데, 이 부분은 내부 코드를 뜯어볼 시간이 없었다..

하지만, 타입정의를 훑어본 결과로는 스타일링 값을 인라인 스타일로 삽입한 컴포넌트를 리턴하는 것으로 확인된다.

그리고 제일 궁금했었던, css 메서드의 호출 결과는 어떻게 되는건지에 대해서 고민을 해봤는데, 결국 이 내용이 리턴하는 값은

read-only 인 배열인 것을 보면, 이 배열을 분해해서 인라인 스타일로 삽입하는 것으로 추론된다.

결론

사실, 내부 구조를 정확하게 뜯어보지 못해서 이렇게 이상한 결론이 날 수밖에 없었고, 해당 에러를 class가 아닌 css 메서드를 통해서 조건부 스타일링을 구현했을 때에는 에러가 존재하지 않았기에 이렇게 임시방편의 방법을 취하였다.

하지만 좀 아쉽다... 도무지 왜 background만 반영이 안되는지는 정확히 확인할 수 없었다. styled component의 구조와 템플릿 리터럴 문법을 배울 수 있었던 계기는 되었지만 좀 찝찝하다.. 다음에 기회가 된다면 꼭 내부 구조를 다 뜯어보고 말 것이다.

reference

styled component의 구조

profile
자라나라 프론트엔드 개발새싹!

0개의 댓글