본문 바로가기
Programming/3. Experience

SCSS에서 Styled-Components로 변환하며 겪은 썰.txt

by @sangseophwang 2022. 5. 16.

입사 후 주어진 첫 과제

 어느덧 입사한 지 2주가 다 될 무렵, 드디어 첫 미션이 주어졌다. Nuxt(Vue)에서 Next(React)로 마이그레이션하는 프로젝트에 푸터 파트를 작업하는 것이었다. 로직도 없고 단순히 마크업만 하는 작업이었기에, '이 정도는 거뜬히 할 수 있지!'라고 생각했지만 예상외의 난관에 부딪히게 됐다. 바로 @mixin을 비롯한 수많은 '@'로 이루어진 SCSS 때문이었다.

 

난관

이 글을 읽는 대다수의 분들이라면 SCSS에 대해 알고 있겠지만, 혹시나 모르는 문들을 위해 간단히 설명하자면 다양한 편의 기능을 갖춘 CSS라고 생각하면 된다. 여러 연산이나 코드의 재사용 등 여러 기능들로 보다 편리하게 스타일 작업을 할 수 있다는 장점이 있지만, Styled-Components로 변환하는 나한테 만큼은 고약한 단점으로 바뀌게 됐는데 그 이유는.. Styled-Components에는 그런 기능이 거의 없었기 때문이었다. 그래서 지금부터는 주말을 반납하며 한 땀 한 땀 작업한 대표 케이스 2가지를 소개하고자 한다.

 

Case 1. 계산이 안되네..?

바로 코드를 보자.

@use 'sass:math';

$large : 2rem;
$size : 5rem;

.test {
  padding-left: percentage(math.div($large, $size));
}

SCSS에서는 'sass:math' 라는 아주 유용한 연산 기능을 제공한다. 맨 아래 padding-left에 적힌 코드를 해석해보자면,

$large$size로 나눠서 퍼센트(%)로 변환해서 padding-left에 적용해주세요!

이런 아주 간단한 내용인데, 처음에 내가 생각한 접근 방식은 다음과 같았다.

...

const test = {
  large: '2rem',
  size: '5rem'
};

padding-left: `calc(${test.large} / ${test.size}) * 100... 어....?

여기서 문제가 몇 가지 있었는데, 그 문제는 이렇다.

  1. math.div()는 SCSS 문법이기에 string 상태의 large와 size를 나눌 수 있도록 CSS 기본 문법인 calc()를 사용하려 했지만, calc()의 조건 중에는 뒷 값 ( a / b <- 바로 이것! )이 무조건 숫자여야만 했다.
  2. percentage() 또한 SCSS의 문법인데, 연산한 후 %를 붙일 방법이 없었다.

이런 문제를 해결하기 위해 아이디어를 냈던건 Styled-Components 내부에서 계산하는 것이 아닌 변수로 따로 계산을 한 다음 값을 넣어주는 방법이었다. 이를 위해서는 우선 rem이 변환된 후 나오는 실제 값을 알아야 했기에 rem 변환의 기준이 되는 최상단 font-size를 알아보기로 했다. font-size를 알아내기 위해서는 아래 코드를 작성하면 된다.

console.log(window.getComputedStyle(document.documentElement).fontSize);

이 코드로 내가 만드는 서비스의 기본 폰트 사이즈를 알아낼 수 있다. 물론 이번 프로젝트에서는 reset을 적용해 'font-size: 62.5%', 즉 10px이라는 것을 미리 알았기 때문에 rem을 변환하지 않고 사용할 수 있다는 사실을 알게 됐다. 그래서 이 난관을 해결했던 방식은 다음과 같다.

const large = '2rem';
const size = '5rem';

const calculatedValue = 
  String(
    (Number(large.substring(0, large.length - 3) / 
    Number(size.substring(0, size.length - 3)) * 100
  ) + '%';
  
  
 ...
 `
 padding-left: $[calculatedValue};
 `

위 코드를 해석하자면 다음과 같다.

  1. string으로 지정된 large와 size 값에서 'rem'을 뺀다.
  2. 뺀 값을 숫자로 바꿔 나눠준 다음 100을 곱한다.
  3. 그리고 그 값을 다시 문자로 바꿔준다.
  4. 바꾼 문자 뒤에 %를 붙여준다.

Styled-Components 특성상 tempate literal로 값을 받기 때문에 위와 같이 연산한 값을 문자로 붙여주는 방식을 사용했더니 원했던 결과가 잘 구현될 수 있었다.

 

Case 2. 미디어 쿼리에 조건이 달린다면?

이 케이스도 바로 코드부터 보도록 하자.

@mixin mediaQuery($default: $large, $maxWidth: null) {
  @if ($maxWidth) {
    @media (max-width: $maxWidth - 1px) {
      @content;
    }
  } @else {
    @media (min-width: $default) {
      @content;
    }
  }
}

이 코드를 해석하자면 다음과 같다.

  1. 미디어 쿼리를 재사용할 건데 조건은 이렇다.
  2. 만약 mediaQuery($maxWidth: 300px)과 같이 $maxWidth 값을 지정해준다면 (max-width: $maxWidth - 1px)로 계산해준다.
  3. 그게 아니라 mediaQuery(300px)이나 mediaQuery()로 설정한다면 인수로 넣은 300px, 혹은 인수를 넣지 않았다면 default 값을 활용해 (min-width: $default)로 계산한다.

@mixin, @if, @else에 인자까지.. 이 문제를 해결하기 위해 공식 문서부터 StackOverflow까지 안 찾아본 페이지가 없을 정도로 여기저기 찾아다녔다. 그러다 알게 된 이 페이지 코드로 해결책을 찾을 수 있었다.

 

https://codesandbox.io/s/media-queries-in-styled-components-typescript-659xq?fontsize=14&hidenavigation=1&theme=dark&file=/src/mediaQueries.ts

 

Media Queries in Styled Components - Typescript - CodeSandbox

Media Queries in Styled Components - Typescript by daggala using @types/react, @types/react-dom, @types/styled-components, react, react-dom, react-scripts, styled-components, typescript

codesandbox.io

export const mediaQueries = (key: keyof typeof breakpoints) => {
  return (style: TemplateStringsArray | String) =>
    `@media (min-width: ${breakpoints[key]}em) { ${style} }`;
};

해석하자면 인자로 breakpoint 값을 받아와서 템플릿 문자열 혹은 string을 받을 수 있는 타입을 style에 적용해 template literal로 미디어 쿼리를 구현하는 것이었다. 이 코드를 응용해 breakpoint가 있는 인자 쪽에 maxWidth 인자를 ?(옵셔널)로 받을 수 있도록 적용해 if/else문으로 동일한 경우의 수를 만들어냈다. SCSS를 사용할 때보다 코드 양이나 복잡도가 늘긴 했지만, 그래도 동일한 기능을 수행할 수 있도록 구현할 수 있었던 케이스였다.

 

결론

위 케이스 외에도 여러 난관들이 있었지만 이 두 사례가 가장 골치 아팠기 때문에 제외했다. 어쨌든! 결국 기존에 만들어놓았던 다양한 재사용 스타일 코드들을 거의 동일하게 구현했고 이를 통해 푸터 또한 동일하게 만들 수 있었다. 주말을 반납한 보람을 온몸으로 느끼는 순간이었다. 이번엔 고작 스타일 코드만을 마이그레이션하는 과정을 겪었지만, 앞으로 Nuxt.js에서 Next.js로 바꾸는 과정에서 또 어떤 일들이 있을지 벌써부터 기대된다. (사실은 기대가 아니라 두려움이 아닌가 싶긴 하지만..) 그래도 이미 상용화된 서비스 코드를 새로운 기술로 변환하는 작업을 살면서 얼마나 해볼 수 있을까. 이번 기회를 통해 많은 것을 배우고, 또 많은 실수를 저지르며 그 속에서 성장할 수 있길 기대해본다.

 

반응형

댓글