HooksuseDebouncedCallback

useDebouncedCallback

ํ•จ์ˆ˜๋ฅผ ๋””๋ฐ”์šด์Šคํ•˜์—ฌ ๋ฐ˜ํ™˜ํ•˜๋Š” Hook์ž…๋‹ˆ๋‹ค. leading, trailing, maxWait ์˜ต์…˜์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

์–ธ์ œ ์‚ฌ์šฉํ•˜๋‚˜์š”?

  • ๐Ÿ” ๊ฒ€์ƒ‰ API ํ˜ธ์ถœ
  • ๐Ÿ–ฑ๏ธ ๋ฒ„ํŠผ ์—ฐํƒ€ ๋ฐฉ์ง€
  • ๐Ÿ’พ ์ž๋™ ์ €์žฅ (ํƒ€์ดํ•‘ ์ค‘)
  • ๐Ÿ“œ ์Šคํฌ๋กค ์ด๋ฒคํŠธ ์ตœ์ ํ™”

๊ธฐ๋ณธ ์‚ฌ์šฉ๋ฒ•

import { useDebouncedCallback } from '@frontend-toolkit-js/hooks';
 
function SearchInput() {
  const debouncedSearch = useDebouncedCallback((query: string) => {
    fetch(`/api/search?q=${query}`);
  }, 300);
 
  return (
    <input
      onChange={e => debouncedSearch(e.target.value)}
      placeholder="๊ฒ€์ƒ‰..."
    />
  );
}

์ฃผ์š” ์‚ฌ์šฉ ์‚ฌ๋ก€

1. ๊ฒ€์ƒ‰ ์ž๋™์™„์„ฑ

function AutocompleteSearch() {
  const [results, setResults] = useState([]);
 
  const debouncedSearch = useDebouncedCallback(async (query: string) => {
    const data = await fetch(`/api/search?q=${query}`);
    setResults(data);
  }, 300);
 
  return <input onChange={e => debouncedSearch(e.target.value)} />;
}

2. ๋ฒ„ํŠผ ์—ฐํƒ€ ๋ฐฉ์ง€ (leading)

function PaymentForm() {
  const handleSubmit = useDebouncedCallback(
    async formData => {
      await submitPayment(formData);
    },
    2000,
    { leading: true, trailing: false }
  );
 
  return <button onClick={handleSubmit}>๊ฒฐ์ œํ•˜๊ธฐ</button>;
}

๋™์ž‘:

ํด๋ฆญ1 (0ms)    โ†’ ์ฆ‰์‹œ ์‹คํ–‰ โœ…
ํด๋ฆญ2 (500ms)  โ†’ ๋ฌด์‹œ โŒ
ํด๋ฆญ3 (1000ms) โ†’ ๋ฌด์‹œ โŒ
ํด๋ฆญ4 (2100ms) โ†’ ๋‹ค์‹œ ์ฆ‰์‹œ ์‹คํ–‰ โœ…

3. ์ž๋™ ์ €์žฅ (maxWait)

function Editor() {
  const [content, setContent] = useState('');
 
  const autoSave = useDebouncedCallback(
    (text: string) => {
      saveToServer(text);
    },
    1000,
    { maxWait: 5000 }
  );
 
  return (
    <textarea
      value={content}
      onChange={e => {
        setContent(e.target.value);
        autoSave(e.target.value);
      }}
    />
  );
}

๋™์ž‘:

๊ณ„์† ํƒ€์ดํ•‘ โ†’ 1์ดˆ ํƒ€์ด๋จธ ๊ณ„์† ๋ฆฌ์…‹
5์ดˆ ๊ฒฝ๊ณผ     โ†’ ๊ฐ•์ œ ์ €์žฅ! โšก
ํƒ€์ดํ•‘ ๋ฉˆ์ถค  โ†’ 1์ดˆ ํ›„ ์ €์žฅ โœ…

4. ์Šคํฌ๋กค ํŠธ๋ž˜ํ‚น (leading + trailing)

function ScrollTracker() {
  const trackScroll = useDebouncedCallback(
    () => {
      analytics.track('scroll', { position: window.scrollY });
    },
    200,
    { leading: true, trailing: true }
  );
 
  useEffect(() => {
    window.addEventListener('scroll', trackScroll);
    return () => window.removeEventListener('scroll', trackScroll);
  }, [trackScroll]);
 
  return <div>...</div>;
}

๋™์ž‘:

์Šคํฌ๋กค ์‹œ์ž‘ โ†’ ์ฆ‰์‹œ ์ถ”์  โœ… (leading)
๊ณ„์† ์Šคํฌ๋กค โ†’ ๋Œ€๊ธฐ...
์Šคํฌ๋กค ๋ฉˆ์ถค โ†’ 200ms ํ›„ ์ถ”์  โœ… (trailing)

API Reference

function useDebouncedCallback<T extends (...args: any[]) => any>(
  callback: T,
  delay: number,
  options?: DebouncedCallbackOptions
): T;

Parameters

callback

๋””๋ฐ”์šด์Šคํ•  ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค.

  • ํƒ€์ž…: (...args: any[]) => any

delay

์ง€์—ฐ ์‹œ๊ฐ„(๋ฐ€๋ฆฌ์ดˆ)์ž…๋‹ˆ๋‹ค.

  • ํƒ€์ž…: number

options

๋””๋ฐ”์šด์Šค ์˜ต์…˜์ž…๋‹ˆ๋‹ค.

interface DebouncedCallbackOptions {
  leading?: boolean; // ์ฒซ ํ˜ธ์ถœ ์ฆ‰์‹œ ์‹คํ–‰
  trailing?: boolean; // ๋งˆ์ง€๋ง‰ ํ˜ธ์ถœ ํ›„ ์‹คํ–‰
  maxWait?: number; // ์ตœ๋Œ€ ๋Œ€๊ธฐ ์‹œ๊ฐ„
}

์˜ต์…˜ ์ƒ์„ธ

leading

์ฒซ ํ˜ธ์ถœ ์‹œ ์ฆ‰์‹œ ์‹คํ–‰ ์—ฌ๋ถ€

// leading: true โ†’ ์ฒซ ํ˜ธ์ถœ ์ฆ‰์‹œ ์‹คํ–‰
const debounced = useDebouncedCallback(fn, 1000, {
  leading: true,
  trailing: false,
});
 
// ์‚ฌ์šฉ ์‚ฌ๋ก€: ๋ฒ„ํŠผ ์—ฐํƒ€ ๋ฐฉ์ง€, ํผ ์ œ์ถœ
  • ๊ธฐ๋ณธ๊ฐ’: false

trailing

๋งˆ์ง€๋ง‰ ํ˜ธ์ถœ ํ›„ ์ง€์—ฐ ์‹œ๊ฐ„ ๋’ค ์‹คํ–‰ ์—ฌ๋ถ€

// trailing: true (๊ธฐ๋ณธ) โ†’ ๋งˆ์ง€๋ง‰ ํ˜ธ์ถœ ํ›„ ์‹คํ–‰
const debounced = useDebouncedCallback(fn, 300);
 
// ์‚ฌ์šฉ ์‚ฌ๋ก€: ๊ฒ€์ƒ‰ ์ž๋™์™„์„ฑ, ์ž…๋ ฅ ๊ฒ€์ฆ
  • ๊ธฐ๋ณธ๊ฐ’: true

maxWait

์ตœ๋Œ€ ๋Œ€๊ธฐ ์‹œ๊ฐ„. ์ด ์‹œ๊ฐ„์ด ์ง€๋‚˜๋ฉด ๊ฐ•์ œ๋กœ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.

// maxWait: 5000 โ†’ ์ตœ๋Œ€ 5์ดˆ๋งˆ๋‹ค ๊ฐ•์ œ ์‹คํ–‰
const debounced = useDebouncedCallback(save, 1000, { maxWait: 5000 });
 
// ์‚ฌ์šฉ ์‚ฌ๋ก€: ์ž๋™ ์ €์žฅ, ์ฃผ๊ธฐ์  ๋™๊ธฐํ™”
  • ๊ธฐ๋ณธ๊ฐ’: undefined

์˜ต์…˜ ์กฐํ•ฉ

๊ธฐ๋ณธ (Trailing๋งŒ)

useDebouncedCallback(fn, 300);
// = { leading: false, trailing: true }
 
// ์ž…๋ ฅ ๋ฉˆ์ถ˜ ํ›„ 300ms ํ›„ ์‹คํ–‰

Leading๋งŒ

useDebouncedCallback(fn, 1000, {
  leading: true,
  trailing: false,
});
 
// ์ฒซ ํ˜ธ์ถœ๋งŒ ์ฆ‰์‹œ ์‹คํ–‰, ์ดํ›„ 1์ดˆ๊ฐ„ ๋ฌด์‹œ

Leading + Trailing

useDebouncedCallback(fn, 200, {
  leading: true,
  trailing: true,
});
 
// ์‹œ์ž‘ ์‹œ ์ฆ‰์‹œ ์‹คํ–‰ + ๋๋‚  ๋•Œ๋„ ์‹คํ–‰

Trailing + MaxWait

useDebouncedCallback(fn, 1000, {
  trailing: true,
  maxWait: 5000,
});
 
// ์ผ๋ฐ˜: 1์ดˆ ๋Œ€๊ธฐ, ๊ฐ•์ œ: 5์ดˆ๋งˆ๋‹ค ์‹คํ–‰

์„ฑ๋Šฅ ํŠน์„ฑ

๋ฒˆ๋“ค ํฌ๊ธฐ

  • ~1.2 KB (minified)
  • ~0.5 KB (gzipped)

๋ฉ”๋ชจ๋ฆฌ

  • ํƒ€์ด๋จธ ์ž๋™ ์ •๋ฆฌ (๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ๋ฐฉ์ง€)
  • cleanup ํ•จ์ˆ˜๋กœ ์–ธ๋งˆ์šดํŠธ ์‹œ ์ •๋ฆฌ

vs useDebounce

ํŠน์ง•useDebounceuseDebouncedCallback
๋””๋ฐ”์šด์Šค ๋Œ€์ƒ๊ฐ’ํ•จ์ˆ˜
๋ฐ˜ํ™˜๊ฐ’ํ•จ์ˆ˜
์˜ต์…˜โŒโœ… leading, trailing, maxWait
์‚ฌ์šฉ ์‚ฌ๋ก€๊ฒ€์ƒ‰์–ด์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ
// useDebounce: ๊ฐ’ ๋””๋ฐ”์šด์Šค
const debouncedValue = useDebounce(value, 300);
 
// useDebouncedCallback: ํ•จ์ˆ˜ ๋””๋ฐ”์šด์Šค
const debouncedFn = useDebouncedCallback(fn, 300);

TypeScript

ํƒ€์ž…์ด ์ž๋™์œผ๋กœ ์ถ”๋ก ๋ฉ๋‹ˆ๋‹ค.

// โœ… ํƒ€์ž… ์ถ”๋ก 
const debouncedSearch = useDebouncedCallback((query: string, limit: number) => {
  fetch(`/api?q=${query}&limit=${limit}`);
}, 300);
 
debouncedSearch('hello', 10); // โœ…
debouncedSearch('hello'); // โŒ ์—๋Ÿฌ: limit ํ•„์š”
debouncedSearch(123, 10); // โŒ ์—๋Ÿฌ: query๋Š” string

์ฃผ์˜์‚ฌํ•ญ

โš ๏ธ ํด๋ฆฐ์—…

์ปดํฌ๋„ŒํŠธ ์–ธ๋งˆ์šดํŠธ ์‹œ ์ž๋™์œผ๋กœ ํƒ€์ด๋จธ๊ฐ€ ์ •๋ฆฌ๋ฉ๋‹ˆ๋‹ค.

// ์ž๋™ cleanup๋จ - ๋ณ„๋„ ์ฒ˜๋ฆฌ ๋ถˆํ•„์š”
const debounced = useDebouncedCallback(fn, 300);

๊ด€๋ จ ๋ฌธ์„œ

  • useDebounce - ๊ฐ’ ๋””๋ฐ”์šด์Šค
  • useThrottle - ์“ฐ๋กœํ‹€๋ง (๊ฐœ๋ฐœ ์˜ˆ์ •)

์ฐธ๊ณ  ์ž๋ฃŒ