[React(Typescript)] resize 이벤트 처리 & in NEXT.js

SeokHun·2021년 2월 17일
0

React

목록 보기
1/4

Window Resize

브라우저 화면 사이즈가 변경될 때 리액트 컴포넌트에서 ReRendering 하고 싶어 window 객체의 resize 이벤트를 찾고 State를 변경해 이를 활용하였다.

Resize 이벤트를 찾게된 이유

학습 목적으로 인스타그램 홈페이지의 기능을 가지고 있는 웹 프로그래밍을 하던 도중 메인 화면에서 프로필을 나타내는 부분이 반응형으로 되어있다는 것을 확인하였고 이를 따라 구현하고 싶었다.

Responsive Web

태플릿, PC, 모바일 등 다양한 해상도로 접근을 하는 사람들이 많아지는 세상에 웹 페이지를 제작할 때 각기 다른 기기에서도 동일한 서비스를 제공하기 위해 반응형으로 만드는 것이 중요하다고 들었다.

Window Resize 이벤트를 추가해 주는 동시에 이를 스타일에 적용할 수 있다면 가능성이 많은 반응형 홈페이지를 만들 수 있을 것이다.

1. React의 addEventListener

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에 해당 내용이 보이게 될 것이다.

2. React Rerendering

설정한 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을 지정하는 상황이라고 가정하고 코드를 작성하였다.

! typeof window !== "undefined" 사용 이유?

window 객체가 정해지지 않았을 때 Stater 값을 지정해 주는 과정이 있기 때문에 오류가 생겼었다.
-> ReferenceError: window is not defined

해당 오류를 고치기 위해 window에 값이 있을 때 이를 사용하도록 임시 방편을 마련했다.

NEXT.JS (SSR) 에서의 오류

위와 같이 코드를 작성하고 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 도 함께 진행하여 브라우저 너비를 인식하도록 변경하였다.

3. Performance improve

위와 같은 상황에서 resize Event는 px 단위로 매번 이벤트 핸들러가 호출된다.
잦은 리렌더는 부드럽게 움직이는 것으로 보이지만 성능을 생각해 React를 사용했는데 무용지물이 된 기분이 드는 것은 어쩔 수 없다...

debounce라는 기술을 이용해 리렌더의 횟수를 줄여 맨 마지막에 결정된 사이즈 값 하나를 사용하도록 해보자.

참고한 사이트에서 Debounce와 Throttle 개념을 여기에서 확인하면 좋다고 한다.

개인적으로 React | 컴포넌트 성능 향상 시키기 (feat. Lodash throttle & debounce)
이 게시글도 유용하다고 생각한다.

디바운스(Debounce)

이벤트를 그룹화해 특정 시간이 지난 후 하나의 이벤트만 발생하도록 하는 기술이다

lodash 패키지가 제공하는 debounce 함수를 사용하여 handleResize 함수가 지정한 시간에 맞추어 실행되도록 해보자.

lodash - npm

❗ 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로 지정하였다.

참고

[React] 리액트에서 resize 이벤트 처리하기

vercel/next.js

디바운스(Debounce)와 스로틀(Throttle ) 그리고 차이점

0개의 댓글