항해 취업 리부트 코스 6기 (사전스터디)-심화 4

Hunter Joe·2024년 11월 4일
0
post-thumbnail

UX 향상

짧은 시간 간격으로 연속해서 이벤트가 발생했을 때 과도한 이벤트 핸들러 호출을 방지하는 기법
setTimeout()을 활용해 구현,원리이해, 적용까지 해보자

  • Throttling
  • Debouncing

Throttling

  • 짧은 시간 동안 연속해서 발생한 이벤트들을 일정시간 단위(delay)를 통해 그룹화 하여 처음 또는 마지막 이벤트 핸들러만 호출되도록 한다. (무한 스크롤에 사용가능)
타입설명
Leading Edge이벤트가 처음 발생할 때 핸들러가 실행되고 주어진 delay동안 동일한 이벤트 무시
Trailing Edge이벤트가 반복적으로 실행될 때 주어진 시간(delay)이 지나면 마지막에 이벤트를 처리
Leading & Trailing Edge처음과 끝에 실행되고 그 사이에 이벤트는 무시 됨

code

const App() {
  let timerId = null;
  
  const throttling = (delay) => {
    // timerId 에 값이 있으면 종료 
    if(timerId) return;
    
	// 실행하고자 하는 로직
    console.log(`API요청 실행, ${delay}ms 동안 추가요청 안받음`);
	
    // timerId에 setTimeout을 걸어놓기 
    timerId = setTimeout(() => {
      console.log(`${delay}ms 지남, 추가 요청 받음`);
      timerId = null;
    }, delay)
  }  
  return(
	<button onClick={() => throttling(2000)}>Throttling Btn</button>
  )
}

Debouncing

  • 짧은 시간간격으로 연속해서 이벤트가 발생하면 이벤트 핸들러를 호출하지 않다가 마지막 이벤트로부터 일정 시간(delay)이 경과한 후에 한번만 호출하도록 하는 것
    주로 입력값 실시간 검색, 화면 resize 이벤트 등에서 사용

code

const App() {
  let timerId = null;
 
  const debounce = (delay) => {
    if(timerId) {
      clearTimeout(timerId);
    }
    
    timerId = setTimeout(() => {
      console.log(`마지막 요청으로부터 ${delay}ms 지났으므로 api 요청 실행`);
      timerId = null;
    }, delay);
  }  
  return(
	<button onClick={() => debounce(2000)}>Debouncing Btn</button>
  )
}

메모리 누수

필요하지 않은 메모리를 계속 점유하고 있는 현상

쓰로틀링과 디바운싱에서 setTimeout을 자주 사용하기 때문에, 이 함수의 사용으로 인한 메모리 누수 가능성을 이해하는 것이 중요

  • 하나의 페이지에서 페이지 이동 없이 setTimeout을 동작시키고 타이머 함수가 종료될 때까지 기다린다면 메모리 누수는 없다.

  • ⭐React로 만든 SPA 앱은 페이지 이동 시 컴포넌트가 언마운트 되는데, 페이지 이동 전에 ⭐setTimeout으로 인해 타이머가 동작중인 상태에서 clearTimeout을 해주지 않고 페이지를 ⭐이동한다면, 컴포넌트는 언마운트 됐음에도 타이머는 여전히 메모리를 차지하고 동작한다.
    이 경우 메모리 누수에 해당한다.

메모리 누수가 있는 코드

const App() {
  let timerId = null;
  
  const throttling = (delay) => {
    if(timerId) return;
    
    console.log(`API요청 실행, ${delay}ms 동안 추가요청 안받음`);
	
    timerId = setTimeout(() => {
      console.log(`${delay}ms 지남, 추가 요청 받음`);
      timerId = null;
    }, delay)
  }  
  
  const debounce = (delay) => {
    if(timerId) {
      clearTimeout(timerId);
    }
    
    timerId = setTimeout(() => {
      console.log(`마지막 요청으로부터 ${delay}ms 지났으므로 api 요청 실행`);
      timerId = null;
    }, delay);
  } 
  
  return(
	<button onClick={() => debounce(2000)}>Debouncing Btn</button>
	<button onClick={() => throttling(2000)}>Throttling Btn</button>
    <button onClick={() => {navigate("/other")}}>다른 페이지로 이동</button>
  )
}
/* debounce, throttling 둘중 아무거나 실행하고 다른 페이지로 이동 버튼을 누른다면 
 해당 컴포넌트가 언마운트 됨에도 불구하고 실행된다. */

이를 해결하기 위해선 useEffect의 cleanup 함수를 이용하면 된다.

const App() {
  let timerId = null;
  
  const throttling = (delay) => {
    if(timerId) return;
    
    console.log(`API요청 실행, ${delay}ms 동안 추가요청 안받음`);
	
    timerId = setTimeout(() => {
      console.log(`${delay}ms 지남, 추가 요청 받음`);
      timerId = null;
    }, delay)
  } 
  
  const debounce = (delay) => {
    if(timerId) {
      clearTimeout(timerId);
    }
    
    timerId = setTimeout(() => {
      console.log(`마지막 요청으로부터 ${delay}ms 지났으므로 api 요청 실행`);
      timerId = null;
    }, delay);
  }  
  
  // useEffect를 사용한 cleanup 
  useEffect(() => {
    return() => {
      if(timerId) {
        clearTimeout(timerId);
      }
    }
  }, []);
  
  return(
	<button onClick={() => debounce(2000)}>Debouncing Btn</button>
	<button onClick={() => throttling(2000)}>Throttling Btn</button>
    <button onClick={() => {navigate("/other")}}>다른 페이지로 이동</button>
  )
}

Lodash

  • JS 유틸리티 라이브러리
  • 배열, 객체 등 데이터 조작을 쉽게 할 수 있는 다양한 함수들을 제공
  • 쓰로틀링과 디바운싱같은 함수도 포함되어 있다.

Lodash 사용한 Debouncing, Throttling 코드 개선

  • 사용 전 후를 비교하면 코드가 굉장히 깔끔 (가독성 올라감)
import _ from 'lodash';
import { useEffect } from 'react'
import { useNavigate } from 'react-router-dom';

const LodashPage = () => {
  const navigate = useNavigate();

  let timerId = null;

  const throttling = _.throttle(() => {
    console.log(`api 요청 실행 2000ms 동안 추가요청 안받음!`);
  }, 2000);

  const debounce = _.debounce(() => {
    console.log(`마지막 요청으로부터 2000ms 지났으므로 api 요청 실행`);
  }, 2000);

  useEffect(() => {
    return () => {
      if(timerId) {
        clearTimeout(timerId);
      }
    }
  }, []);
  

  return (
    <>
      <div>Lodash</div>
      <button onClick={throttling}>쓰로틀링 버튼</button>
      <button onClick={debounce}>디바운싱 버튼</button>
      <div>
        <button onClick={() => {navigate("/")}}>Home페이지 이동</button>
        <button onClick={() => {navigate("/lodash")}}>lodash연습페이지 이동</button>
      </div>
    </>
  )
}

export default LodashPage

디바운싱 예제 (입력값 디바운싱 테스트)

import _ from "lodash";
import { useState, useCallback } from "react";
const Company = () => {
  const [searchText, setSearchText] = useState("");
  const [inputText, setInputText] = useState("");

  // 둘다 실행해서 차이점을 알아 둘 것 
  // const handleSearchText = _.debounce((text) => setSearchText(text), 2000);
  
  const handleSearchText = useCallback(_.debounce((text) => setSearchText(text), 200),[]);

  // + useCallback : memoization인데 이제 함수를 저장! 
  // + useMemo : 값을 memoization  

  const handleChange = (e) => {
    setInputText(e.target.value);
    handleSearchText(e.target.value);
  };

  return (
    <div
      style={{
        paddingLeft: 20,
        paddingRight: 20,
      }}
    >
      <h1>디바운싱 예제</h1>
      <br />
      <input
        placeholder="입력값을 넣고 디바운싱 테스트를 해보세요."
        style={{ width: "300px" }}
        onChange={handleChange}
        type="text"
      />
      <p>Search Text: {searchText}</p>
      <p>Input Text: {inputText}</p>
    </div>
  );
}

export default Company
profile
두 or 다이 / FE 목표
post-custom-banner

0개의 댓글