입사 후 주어진 첫 과제
어느덧 입사한 지 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... 어....?
여기서 문제가 몇 가지 있었는데, 그 문제는 이렇다.
- math.div()는 SCSS 문법이기에 string 상태의 large와 size를 나눌 수 있도록 CSS 기본 문법인 calc()를 사용하려 했지만, calc()의 조건 중에는 뒷 값 ( a / b <- 바로 이것! )이 무조건 숫자여야만 했다.
- 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};
`
위 코드를 해석하자면 다음과 같다.
- string으로 지정된 large와 size 값에서 'rem'을 뺀다.
- 뺀 값을 숫자로 바꿔 나눠준 다음 100을 곱한다.
- 그리고 그 값을 다시 문자로 바꿔준다.
- 바꾼 문자 뒤에 %를 붙여준다.
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;
}
}
}
이 코드를 해석하자면 다음과 같다.
- 미디어 쿼리를 재사용할 건데 조건은 이렇다.
- 만약 mediaQuery($maxWidth: 300px)과 같이 $maxWidth 값을 지정해준다면 (max-width: $maxWidth - 1px)로 계산해준다.
- 그게 아니라 mediaQuery(300px)이나 mediaQuery()로 설정한다면 인수로 넣은 300px, 혹은 인수를 넣지 않았다면 default 값을 활용해 (min-width: $default)로 계산한다.
@mixin, @if, @else에 인자까지.. 이 문제를 해결하기 위해 공식 문서부터 StackOverflow까지 안 찾아본 페이지가 없을 정도로 여기저기 찾아다녔다. 그러다 알게 된 이 페이지 코드로 해결책을 찾을 수 있었다.
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로 바꾸는 과정에서 또 어떤 일들이 있을지 벌써부터 기대된다. (사실은 기대가 아니라 두려움이 아닌가 싶긴 하지만..) 그래도 이미 상용화된 서비스 코드를 새로운 기술로 변환하는 작업을 살면서 얼마나 해볼 수 있을까. 이번 기회를 통해 많은 것을 배우고, 또 많은 실수를 저지르며 그 속에서 성장할 수 있길 기대해본다.
'Programming > 3. Experience' 카테고리의 다른 글
useReducer + custom hook으로 state 관리하기 (0) | 2024.07.26 |
---|---|
emotion to scss module 도입기 (0) | 2024.07.25 |
서비스에 감정 불어넣기! @emotion 적용기 (0) | 2022.11.26 |
[연습] Momentum 응용하기 (0) | 2021.06.23 |
[연습] HTML, CSS로 계산기 만들기 (1) | 2021.05.02 |
댓글