브라우저 화면 사이즈가 변경될 때 리액트 컴포넌트에서 ReRendering 하고 싶어 window 객체의 resize 이벤트를 찾고 State를 변경해 이를 활용하였다.
학습 목적으로 인스타그램 홈페이지의 기능을 가지고 있는 웹 프로그래밍을 하던 도중 메인 화면에서 프로필을 나타내는 부분이 반응형으로 되어있다는 것을 확인하였고 이를 따라 구현하고 싶었다.
태플릿, PC, 모바일 등 다양한 해상도로 접근을 하는 사람들이 많아지는 세상에 웹 페이지를 제작할 때 각기 다른 기기에서도 동일한 서비스를 제공하기 위해 반응형으로 만드는 것이 중요하다고 들었다.
Window Resize 이벤트를 추가해 주는 동시에 이를 스타일에 적용할 수 있다면 가능성이 많은 반응형 홈페이지를 만들 수 있을 것이다.
JS에서 addEventListener 함수를 사용하는 것은 Event에 대한 하나의 반응을 추가하는 것이다.
현재 하고싶은 방향은 하나의 resize Event를 추가하고 싶은 것이므로 컴포넌트가 처음 마운트 될 때 addEventListener를 해주면 될 것이다.
useEffect 훅의 두 번째 매개변수에 빈 배열을 넘겨준다면?
-> 컴포넌트가 마운트 될 때만 첫 번째 매개변수로 입력된 함수가 호출되고 컴포넌트가 언마운트 될 때만 반환된 함수가 호출된다.
import React, { useEffect } from 'react';
const RSComponent: React.FC = () => {
const handleResize = () => {
console.log(`브라우저 화면 사이즈 x: ${window.innerWidth}, y: ${window.innerHeight}`);
}
useEffect(() => {
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
}
}, []);
return (
<div>
브라우저 화면 너비 : {window.innerWidth}
</div>
);
}
자원의 활용을 위해 라이프사이클 마지막에 이벤트를 삭제해 주는 것이다.
위의 코드를 활용해보면 resize Event가 발생할 때마다 console에 해당 내용이 보이게 될 것이다.
설정한 resize Event 가 발생될 때 변경된 값을 Rerendering 할 예정이다.
React에서 Rerendering 되는 것은 State, Props 값이 변경될 때 이므로 State를 추가해 이를 제어해보도록 하자.
import React, { useEffect, useState } from 'react';
const RSComponent: React.FC = () => {
const [windowWidthSize, setWindowWidthSize] = useState<number>(
typeof window !== "undefined" ? window.innerWidth : 0
);
const handleResize = () => {
setWindowWidthSize(window.innerWidth);
}
useEffect(() => {
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
return windowWidthSize >= 1000
? (
<div>
브라우저 화면 너비 : {windowWidthSize}
</div>
)
: null;
}
1000px 보다 작아지면 rendering 하지 않고, 나머지 상황에서는 margin을 지정하는 상황이라고 가정하고 코드를 작성하였다.
window 객체가 정해지지 않았을 때 Stater 값을 지정해 주는 과정이 있기 때문에 오류가 생겼었다.
-> ReferenceError: window is not defined
해당 오류를 고치기 위해 window에 값이 있을 때 이를 사용하도록 임시 방편을 마련했다.
위와 같이 코드를 작성하고 NEXT.js 에서 실행해보면 다음과 같은 오류가 발생했었다.
Warning: Expected server HTML to contain a matching <div> in <div>
확실하지 않지만 나의 생각으로는 서버에서 브라우저 너비를 확인한 후 렌더링하도록 하는 구성이 이와 같은 오류를 발생시켰다고 짐작하고 있다.
즉 Server Side Rendering에서 브라우저 너비를 인식하지 못하기 때문이라고 생각한다.
import React, { useEffect, useState } from 'react';
const RSComponent: React.FC = () => {
const [windowWidthSize, setWindowWidthSize] = useState<number>(1000);
const handleResize = () => {
setWindowWidthSize(window.innerWidth);
}
useEffect(() => {
setWindowWidthSize(window.innerWidth);
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
return windowWidthSize >= 1000
? (
<div>
브라우저 화면 너비 : {windowWidthSize}
</div>
)
: null;
}
브라우저에서만 실행되어야하는 코드를 내부에서 실행되어야 한다고 생각해 addEventListner 를 추가할 때 setState 도 함께 진행하여 브라우저 너비를 인식하도록 변경하였다.
위와 같은 상황에서 resize Event는 px 단위로 매번 이벤트 핸들러가 호출된다.
잦은 리렌더는 부드럽게 움직이는 것으로 보이지만 성능을 생각해 React를 사용했는데 무용지물이 된 기분이 드는 것은 어쩔 수 없다...
debounce라는 기술을 이용해 리렌더의 횟수를 줄여 맨 마지막에 결정된 사이즈 값 하나를 사용하도록 해보자.
참고한 사이트에서 Debounce와 Throttle 개념을 여기에서 확인하면 좋다고 한다.
개인적으로 React | 컴포넌트 성능 향상 시키기 (feat. Lodash throttle & debounce)
이 게시글도 유용하다고 생각한다.
이벤트를 그룹화해 특정 시간이 지난 후 하나의 이벤트만 발생하도록 하는 기술이다
lodash 패키지가 제공하는 debounce 함수를 사용하여 handleResize 함수가 지정한 시간에 맞추어 실행되도록 해보자.
❗ React의 경우
npm install lodash
❗ TypeScript React 경우
npm install @types/lodash
import React, { useEffect, useState } from 'react';
import { debounce } from 'lodash';
const RSComponent: React.FC = () => {
const [windowWidthSize, setWindowWidthSize] = useState<number>(1000);
const handleResize = debounce(() => {
setWindowWidthSize(window.innerWidth);
}, 25);
useEffect(() => {
setWindowWidthSize(window.innerWidth);
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
return windowWidthSize >= 1000
? (
<div>
브라우저 화면 너비 : {windowWidthSize}
</div>
)
: null;
}
resize 시에 부드럽게 적용하기 위해 25ms로 지정하였다.