프로젝트를 마치고 본인이 작성했던 코드를 리뷰하면서 블로그를 썼기 때문에 부자연스러운 부분이 있을지도 모른다.
처음 각각의 페이지를 구현할 때는 Responsive UI를 고려하지 않고 만들었기 때문에, 고생했던 기억이 있어서, 다음에 프로젝트를 진행한다면 시간이 많이 절약될 것 같다...
어느정도 페이지가 완성되어 갈 쯤, 아래의 SCSS코드로 break point를 1200px로 지정해서 브라우저의 width가 1200px보다 작아지면 모바일 화면으로 바뀌게끔 설정하였다.
$breakpoint: 1200px;
@mixin mobile {
@media (max-width: #{$breakpoint - 1px}) {
@content;
}
}
@mixin desktop {
@media (min-width: #{$breakpoint}) {
@content;
}
}
// 예시
@include mobile {
width: calc(100vw - 32px);
margin-top: 60px;
}
@include desktop {
width: 380px;
margin-top: 80px;
}
그런데 문제는 SCSS가 아닌 TSX 파일에 있었다.
처음 구현해보는 모바일 화면이라 많이 헤맸는데, 가장 큰 이유는 className에 따라, 또는 별도로 작성하여 불러온 컴포넌트의 prop값에 따라 변하는 style요소였다.
그래서 창의 크기가 변할 때마다 상태가 업데이트 될 수 있도록 isMobile이라는 변수를 통해 조건부 렌더링으로 구현했는데, 찾아보니 내 방식과 동일한 useWindowSize라는 custom hook이 있어서 useWindowSize.ts 파일로 분리해서 아래와 같이 작성하였다.
// useWindowSize.ts
import { useState, useEffect } from "react";
const useWindowSize = () => {
const [windowWidth, setWindowWidth] = useState<number>(window.innerWidth);
useEffect(() => {
const handleResize = () => {
setWindowWidth(window.innerWidth);
};
window.addEventListener("resize", handleResize);
return () => {
window.removeEventListener("resize", handleResize);
};
}, []);
return windowWidth;
};
export default useWindowSize;
// isMobile 변수 선언
import useWindowSize from "utils/useWindowSize";
const isMobile = useWindowSize() < 1200 ? true : false;
// 조건부 렌더링 예시
<div className={isMobile ? "font-title-small" : "font-title-medium"}>
내가 생각했던 대로 정상적으로 잘 작동했고, 그대로 프로젝트를 끝마쳤었다.
프로젝트가 끝나고 며칠 뒤, 우연히 알게 된 것으로 반응형 웹을 구현할 때에는 최대한 CSS만을 통해 (media query) 구현하는 것이 더 명시적이고 성능 면에서도 약간 더 우세하다고 한다.
그래서 수정할 방법이 없을까 고민하다가 발견한 것이 바로 useMediaQuery라는 custom hook이었다.
기존의 코드에서 useWindowSize 대신 useMediaQuery를 사용해서 아래와 같이 리팩토링 해보았다.
// useMediaQuery.ts
import { useEffect, useState } from "react";
const useMediaQuery = (query: string) => {
const [matches, setMatches] = useState(window.matchMedia(query).matches);
useEffect(() => {
const mediaQuery = window.matchMedia(query);
const handleChange = (event: MediaQueryListEvent | MediaQueryList) => {
setMatches(event.matches);
};
mediaQuery.addEventListener("change", handleChange);
handleChange(mediaQuery);
return () => mediaQuery.removeEventListener("change", handleChange);
}, [query]);
return matches;
};
export default useMediaQuery;
// isMobile 변수 선언
import useMediaQuery from "utils/useMediaQuery";
const isMobile = useMediaQuery("(max-width: 1200px)");
엄밀히 말하면 동작하는 방식이 조금 다른데,
useWindowSize를 사용하면 창의 너비가 변할 때마다 추적하고,
isMobile 변수가 그 값이 1200보다 작은지에 따라 true or false로 결정되고,
useMediaQuery를 사용하면 단순히 창의 크기 뿐만 아니라, 여러 미디어쿼리 조건에 따라 현재와 일치하는지 여부를 판별해서 직접 isMobile 변수의 값을 정하는 것이다.
그래서, 두가지 모두를 사용하는 경우도 있고, 프로젝트에 따라 필요한 방법을 사용한다고 한다.
몰랐던 사실을 알고 내가 작성했던 코드에 적용해 보는 것도 재미있었고, 한층 더 성장한 것 같아서 유익하고 기분좋은 시간이었다!