1. Why are they necessary? πŸ‘©β€πŸ’»

μš”μ¦˜ κ°œλ°œμ€ Reactive Programming 이 λŒ€μ„Έκ°€ 되며 Event Listening 기반의 ν”„λ‘œκ·Έλž˜λ°μ΄ λ§Žμ•„μ‘Œλ‹€. μ΄λŸ¬ν•œ 이벀트 기반 ν”„λ‘œκ·Έλž˜λ°μ€ 쒋은 UX λ₯Ό μ œκ³΅ν•  수 μžˆμœΌλ‚˜ κ³Όλ„ν•œ Event λŠ” 였히렀 μ„±λŠ₯ μ €ν•˜λ‘œ 이어지고, UX 에 λ‚˜μœ 영ν–₯을 λ―ΈμΉ  수 μžˆλ‹€.

Throttle κ³Ό Debounce κ°€ 주둜 μ‚¬μš©λ˜λŠ” λŒ€ν‘œμ μΈ 예λ₯Ό 톡해 μ‚΄νŽ΄λ³΄μž.

1. Throttle

Throttle 은 μ΅œμ†Œ μž¬μž…λ ₯ μ‹œκ°„μ„ μ£ΌλŠ” 것과 κ°™λ‹€. κ°€μž₯ μ‰¬μš΄ μ˜ˆλ‘œλŠ” ν‚€λ³΄λ“œ 섀정에 μžˆλŠ” ν‚€ 반볡 속도닀. ν‚€ 반볡 속도가 느리면 동일 μ‹œκ°„ ν‚€λ₯Ό λˆ„λ₯΄κ³  μžˆμ–΄λ„ 반볡 속도가 λΉ λ₯Ό λ•Œλ³΄λ‹€ μ‹€μ œ μž…λ ₯이 적게 λœλ‹€.

μ›Ήμ—μ„œλŠ” μ–΄λ–€ κ²½μš°μ— μ‚¬μš©ν• κΉŒ?

κ°€μž₯ 많이 μ‚¬μš©λ˜λŠ” μ˜ˆλŠ” λ°”λ‘œ λΈŒλΌμš°μ € κ΄€λ ¨ μ΄λ²€νŠΈλ‹€. 화면을 슀크둀 ν•˜κ±°λ‚˜ 화면을 λ¦¬μ‚¬μ΄μ¦ˆ ν•˜λŠ” κ²½μš°μ— λ°œμƒν•˜λŠ” μ΄λ²€νŠΈλŠ” λ¦¬μŠ€λ„ˆμ— μ½˜μ†”μ„ 좜λ ₯해보면 μ—„μ²­λ‚˜κ²Œ λ§Žμ€ μ΄λ²€νŠΈκ°€ μˆ˜μ‹ λ˜λŠ” 것을 μ•Œ 수 μžˆλ‹€. ν•˜μ§€λ§Œ 이 μˆ˜λ§Žμ€ 이벀트λ₯Ό λͺ¨λ‘ μ²˜λ¦¬ν•˜λ©΄ 였히렀 μ„±λŠ₯ λ¬Έμ œκ°€ 생기고 이둜 인해 쒋지 μ•Šμ€ UX κ°€ λ˜μ–΄λ²„λ¦°λ‹€.

μˆ˜μ—†μ΄ λ°œμƒλ˜λŠ” 이벀트λ₯Ό 일정 μ£ΌκΈ°λ§ˆλ‹€ μ²˜λ¦¬ν•˜κΈ° μœ„ν•΄ Throttle 처리λ₯Ό ν•΄μ£Όλ©΄ Reactive Programming 을 μœ μ§€ν•˜λ©΄μ„œ 처리 횟수λ₯Ό 쀄여 μ—„μ²­λ‚œ μ„±λŠ₯ κ°œμ„ μ„ ν•  수 μžˆλ‹€.

이 외에도 검색창에 ν‚€λ³΄λ“œ μž…λ ₯μ‹œλ§ˆλ‹€ μžλ™μ™„μ„± λͺ©λ‘μ„ λΆˆλŸ¬μ˜€λŠ” κ²½μš°μ—λ„ 일정 μž…λ ₯ μ£ΌκΈ°λ§ˆλ‹€ κ²°κ³Όλ₯Ό λ³€κ²½ν•΄ μ„±λŠ₯ κ°œμ„ μ„ ν•  수 있고, Post 와 같은 μš”μ²­μ΄ μ•„λ‹Œ μ—¬λŸ¬ 번 전솑이 κ°€λŠ₯ν•œ Get μš”μ²­μ˜ 경우 κ³ μž₯λ‚œ 마우슀 λ“±μœΌλ‘œ 인해 μ—°μ†μœΌλ‘œ 2번 μš”μ²­μ΄ 될 수 μžˆλŠ” κ²½μš°μ— Throttle 을 μΆ”κ°€ν•΄ μ‚¬μš©μž μ˜λ„κ°€ μ•„λ‹Œ λΆˆν•„μš”ν•œ 쀑볡 μš”μ²­μ„ λ§‰λŠ” 데 μ‚¬μš©λ˜κΈ°λ„ ν•œλ‹€.

2. Debounce

Debounce λŠ” 싀행을 μ§€μ—°μ‹œν‚€λŠ” 효과λ₯Ό λ‚Έλ‹€. Throttle 은 첫 번째 싀행은 μ¦‰μ‹œ 이루어지고, 이후 μš”μ²­κ±΄μ— λŒ€ν•΄ 일정 μ‹œκ°„ λ™μ•ˆ μš”μ²­μ„ Block μ‹œν‚¨λ‹€. λ”°λΌμ„œ 일정 μ‹œκ°„μ΄ μ§€λ‚˜λ©΄ μž¬μš”μ²­μ΄ κ°€λŠ₯ν•˜λ‹€. 반면 Debounce λŠ” μ—°μ†λœ μš”μ²­κ±΄μ΄ λ“€μ–΄μ˜¬ λ•Œ 일정 μ‹œκ°„ μš”μ²­μ΄ μ€‘λ‹¨λ˜λ©΄ 싀행을 ν•œλ‹€. λ”°λΌμ„œ μ‹€ν–‰ 지연 μ‹œκ°„λ³΄λ‹€ 짧은 주기둜 지속적인 μš”μ²­κ±΄μ΄ λ°œμƒν•˜λ©΄ κ³„μ†ν•΄μ„œ 싀행을 ν•˜μ§€ μ•ŠλŠ”λ‹€.

이것 μ—­μ‹œ 검색창에 주둜 μ‚¬μš©λœλ‹€. Throttle 같은 κ²½μš°λŠ” 쀑간 μž…λ ₯ κ²°κ³Όκ°€ μ˜λ―Έκ°€ μžˆλŠ” 경우 μ‚¬μš©μžμ—κ²Œ 지속적인 ν”Όλ“œλ°±μ„ μ£ΌκΈ° μœ„ν•΄ μ‚¬μš©λ˜μ§€λ§Œ 이 κ²½μš°λŠ” 쀑간 μž…λ ₯ κ²°κ³Όκ°€ λ¬΄μ˜λ―Έν•œ 경우 μ‚¬μš©ν•˜λ©΄ μ’‹λ‹€. μ–΄μ°¨ν”Ό 쀑간 μž…λ ₯ κ²°κ³Όκ°€ λ¬΄μ˜λ―Έν•˜λ‹€λ©΄ ꡳ이 λ§Žμ€ μš”μ²­μ„ 보낼 ν•„μš”κ°€ μ—†κΈ° λ•Œλ¬Έμ΄λ‹€.

μ˜ν™” 검색을 예둜 λ“€μ–΄λ³΄μž. 검색창에 μ˜ν™”μ˜ 제λͺ©μ„ μž…λ ₯ν•  λ•Œ μ‚¬μš©μžμ˜ μž…λ ₯을 돕기 μœ„ν•΄ 검색 ν…μŠ€νŠΈ μžλ™μ™„μ„±μ€ Throttle 을 μ΄μš©ν•΄ 주기적으료 μš”μ²­μ„ 보내도둝 μ²˜λ¦¬ν•˜λ©΄ μ’‹λ‹€. ν•˜μ§€λ§Œ ν…μŠ€νŠΈμ™€ 달리 이미지λ₯Ό λΆˆλŸ¬μ˜€λŠ” 것은 λ§Žμ€ μ–‘μ˜ 데이터λ₯Ό ν•„μš”λ‘œ ν•˜λ©°, μ‚¬μš©μžλŠ” 아직 검색을 μœ„ν•΄ μ˜ν™” 제λͺ©μ„ μž…λ ₯쀑이기 λ•Œλ¬Έμ— μƒλŒ€μ μœΌλ‘œ μ˜ν™” ν¬μŠ€ν„°μ— λŒ€ν•œ κ΄€μ‹¬λ„λŠ” 떨어진닀. λ”°λΌμ„œ μ˜ν™” ν¬μŠ€ν„°λŠ” μ‚¬μš©μžκ°€ 일정 μ‹œκ°„ μž…λ ₯이 μ •μ§€λ˜μ—ˆμ„ λ•Œ ν•œ λ²ˆμ”© μ—…λ°μ΄νŠΈ ν•˜λ„λ‘ ν•˜λŠ” 것이 λ”μš± νš¨μœ¨μ μ΄λ‹€. λ”°λΌμ„œ 이 경우 Denounce λ₯Ό μ‚¬μš©ν•΄ μ²˜λ¦¬ν•˜λ©΄ μ„±λŠ₯을 높이고 톡신 및 μ„œλ²„ λΉ„μš©μ„ 쀑일 수 μžˆλ‹€.


2. Implementation - Throttle πŸ‘©β€πŸ’»

λ‚΄λΆ€ 컀링 ν•¨μˆ˜κ°€ ν•¨μˆ˜ 선언식이냐 ν‘œν˜„μ‹μ΄λƒμ— 따라 this binding 이 ν•„μš”ν•΄ 쑰금 λ‹€λ₯΄κ²Œ κ΅¬ν˜„ν•΄μ•Όν•œλ‹€. JavaScript 와 TypeScript λ²„μ „μœΌλ‘œ 각각 κ΅¬ν˜„ λ‚΄μš©μ„ μ‚΄νŽ΄λ³Έλ‹€.

1. JavaScript

  • Function Declaration
export const throttle = (fn, delay = 500) => {
  let available = true;

  return function () {
    if (available) {
      available = false;
      fn.apply(this, arguments);
      setTimeout(() => {
        available = true;
      }, delay);
    }
  };
};
  • Function Expression
export const throttle = (fn, delay = 500) => {
  let available = true;

  return (...args) => {
    if (available) {
      available = false;
      fn(...args);
      setTimeout(() => {
        available = true;
      }, delay);
    }
  };
};

2. TypeScript

  • Function Declaration
export const throttle = (fn: Function, delay = 500) => {
  let available = true;

  return function $fn() {
    if (available) {
      available = false;
      fn.apply($fn, arguments);
      setTimeout(() => {
        available = true;
      }, delay);
    }
  };
};

TypeScript μ—μ„œ applyλ₯Ό μ •μ˜ν•  λ•Œ thisλ₯Ό μ‚¬μš©ν•˜λ©΄ Lint μ—μ„œ any Types λ₯Ό 가급적 μ‚¬μš©ν•˜μ§€ 말라고 μ•Œλ €μ£ΌλŠ” 것 λ•Œλ¬Έμ— // @ts-ignoreλ₯Ό λͺ…μ‹œν•˜κ±°λ‚˜ thisκ°€ μ•„λ‹Œ μ •ν™•νžˆ λͺ…μ‹œλœ λŒ€μƒμ„ μ œκ³΅ν•΄μ•Όν•œλ‹€.

λ¬΄μ‹œν•˜λ„λ‘ 주석 처리λ₯Ό ν•˜λŠ” 것 λ³΄λ‹€λŠ” $fnμ΄λΌλŠ” 이름을 μ£ΌλŠ” κ²ƒμœΌλ‘œ μ²˜λ¦¬ν–ˆλ‹€.

  • Function Expression
export const throttle = (fn: Function, delay = 500) => {
  let available = true;

  return (...args: unknown[]) => {
    if (available) {
      available = false;
      fn(...args);
      setTimeout(() => {
        available = true;
      }, delay);
    }
  };
};


λ‹€μŒκ³Ό 같이 μ‚¬μš©ν•œλ‹€.

const throttledFoo = throttle(foo)
const throttledBar = throttle(bar, 2000)

3. Implementation - Debounce πŸ‘©β€πŸ’»

λ‚΄λΆ€ 컀링 ν•¨μˆ˜κ°€ ν•¨μˆ˜ 선언식이냐 ν‘œν˜„μ‹μ΄λƒμ— 따라 this binding 이 ν•„μš”ν•΄ 쑰금 λ‹€λ₯΄κ²Œ κ΅¬ν˜„ν•΄μ•Όν•œλ‹€. JavaScript 와 TypeScript λ²„μ „μœΌλ‘œ 각각 κ΅¬ν˜„ λ‚΄μš©μ„ μ‚΄νŽ΄λ³Έλ‹€.

1. JavaScript

  • Function Declaration
export const debounce = (fn, delay = 500) => {
  let timer;

  return function () {
    clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(this, arguments);
    }, delay);
  };
};
  • Function Expression
export const debounce = (fn, delay = 500) => {
  let timer;

  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => {
      fn(...args);
    }, delay);
  };
};


2. TypeScript

  • Function Declaration
export const debounce = (fn: Function, delay = 500) => {
  let timer: ReturnType<typeof setTimeout>;

  return function $fn() {
    clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply($fn, arguments);
    }, delay);
  };
};

TypeScript μ—μ„œ applyλ₯Ό μ •μ˜ν•  λ•Œ thisλ₯Ό μ‚¬μš©ν•˜λ©΄ Lint μ—μ„œ any Types λ₯Ό 가급적 μ‚¬μš©ν•˜μ§€ 말라고 μ•Œλ €μ£ΌλŠ” 것 λ•Œλ¬Έμ— // @ts-ignoreλ₯Ό λͺ…μ‹œν•˜κ±°λ‚˜ thisκ°€ μ•„λ‹Œ μ •ν™•νžˆ λͺ…μ‹œλœ λŒ€μƒμ„ μ œκ³΅ν•΄μ•Όν•œλ‹€.

λ¬΄μ‹œν•˜λ„λ‘ 주석 처리λ₯Ό ν•˜λŠ” 것 λ³΄λ‹€λŠ” $fnμ΄λΌλŠ” 이름을 μ£ΌλŠ” κ²ƒμœΌλ‘œ μ²˜λ¦¬ν–ˆλ‹€.

  • Function Expression
export const debounce = (fn: Function, delay = 500) => {
  let timer: ReturnType<typeof setTimeout>;

  return (...args: unknown[]) => {
    clearTimeout(timer);
    timer = setTimeout(() => {
      fn(...args);
    }, delay);
  };
};


λ‹€μŒκ³Ό 같이 μ‚¬μš©ν•œλ‹€.

const debouncedFoo = debounce(foo)
const debouncedBar = debounce(bar, 2000)

4. Examples πŸ‘©β€πŸ’»

Throttle

0


Debounce

0

5. React - Hooks πŸ‘©β€πŸ’»

1. useThrottle, useDebounce

React μ—μ„œλŠ” μ»΄ν¬λ„ŒνŠΈμ˜ Life Cycle κ³Ό λ Œλ”λ§μœΌλ‘œ 인해 순수 ν•¨μˆ˜λ‘œ μ‚¬μš©ν•˜λŠ” 것 보닀 useState와 useEffectλ₯Ό μ‚¬μš©ν•΄ Custom Hooks 둜 κ΅¬ν˜„ν•˜λŠ” 것이 μ’‹λ‹€.

κ·Έ 쀑 useThrottle κ³Ό useDebounce λŠ” useState의 value λ₯Ό λ°›μ•„μ„œ Debounce, Throttle 효과λ₯Ό μΆ”κ°€ν•˜κΈ° μœ„ν•΄ μ‚¬μš©λœλ‹€.

  • useThrottle.ts
import { useEffect, useRef, useState } from "react";

const useThrottle = <T>(value: T, delay = 500): T => {
  const [throttledValue, setThrottledValue] = useState(value);
  const available = useRef(true);

  useEffect(() => {
    if (available.current) {
      available.current = false;
      setThrottledValue(value);
      setTimeout(() => (available.current = true), delay);
    }
  }, [value, delay]);

  return throttledValue;
};

export default useThrottle;
  • useDebounce.ts
import { useEffect, useState } from "react";

const useDebounce = <T>(value: T, delay = 500): T => {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    const timer = setTimeout(() => setDebouncedValue(value), delay);

    return () => clearTimeout(timer);
  }, [value, delay]);

  return debouncedValue;
};

export default useDebounce;


useThrottle 훅은 슀크둀 μ΄λ²€νŠΈλ‚˜ μœˆλ„μš° λ¦¬μ‚¬μ΄μ¦ˆ μ΄λ²€νŠΈμ™€ 같은 것에 μŠ€λ‘œν‹€μ„ κ±°λŠ” 데 μ‚¬μš©ν•  수 μžˆλ‹€.

const [scrollPosition, setScrollPosition] = useState<number>(window.scrollY);
const throttledScrollPosition = useThrottle<number>(scrollPosition, 500);

useDebounce 훅은 κ°’μ˜ μž…λ ₯에 λ”°λ₯Έ API μš”μ²­ 지연, λ Œλ”λ§ 지연과 같은 것에 μ‚¬μš©ν•  수 μžˆλ‹€.

const [searchTerm, setSearchTerm] = useState<string>('');
const debouncedSearchTerm = useDebounce<string>(searchTerm, 500);

2. useThrottleFn, useDebounceFn

  • useThrottleFn.ts
import { useRef } from "react";

const useThrottleFn = (fn: Function, delay = 500) => {
  const available = useRef(true);

  return (...args: unknown[]) => {
    if (available.current) {
      available.current = false;
      fn(...args);
      setTimeout(() => {
        available.current = true;
      }, delay);
    }
  };
};

export default useThrottleFn;
  • useDebounceFn.ts
import { useRef } from "react";

const useDebounceFn = (fn: Function, delay = 500) => {
  const timer = useRef<ReturnType<typeof setTimeout>>();

  return (...args: unknown[]) => {
    clearTimeout(timer.current);

    timer.current = setTimeout(() => {
      fn(...args);
    }, delay);
  };
};

export default useDebounceFn;

사싀 ν•¨μˆ˜μ— μŠ€λ‘œν‹€μ΄λ‚˜ λ””λ°”μš΄μŠ€λ₯Ό μ μš©ν•˜λŠ” 것은 Closures 에 μ˜ν•΄ 격리된 λ©”λͺ¨λ¦¬ 곡간에 λ³€μˆ˜κ°€ μ•ˆμ „ν•˜κ²Œ μ €μž₯되기 λ•Œλ¬Έμ— ꡳ이 useThrottleFn, useDebounceFn을 μ‚¬μš©ν•  ν•„μš”λŠ” μ—†λ‹€. κ·Έλƒ₯ μœ„μ—μ„œ μž‘μ„±ν•œ throttle와 debounce의 TypeScript 버전을 μ‚¬μš©ν•΄λ„ λœλ‹€.