HooksuseDebounce

useDebounce

๊ฐ’์˜ ๋ณ€๊ฒฝ์„ ์ง€์—ฐ์‹œ์ผœ ๋””๋ฐ”์šด์Šค๋œ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๋Š” Hook์ž…๋‹ˆ๋‹ค.

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

  • ๐Ÿ” ๊ฒ€์ƒ‰ ์ž…๋ ฅ ์ž๋™์™„์„ฑ
  • โŒจ๏ธ ์ž…๋ ฅ ๊ฒ€์ฆ (ํƒ€์ดํ•‘ ์™„๋ฃŒ ํ›„ ์‹คํ–‰)
  • ๐Ÿ“Š ์‹ค์‹œ๊ฐ„ ํ•„ํ„ฐ๋ง
  • ๐Ÿ’พ ์ž๋™ ์ €์žฅ (์ž…๋ ฅ ๋ฉˆ์ถ˜ ํ›„)

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

import { useDebounce } from '@frontend-toolkit-js/hooks';
 
function SearchInput() {
  const [search, setSearch] = useState('');
  const debouncedSearch = useDebounce(search, 300);
 
  useEffect(() => {
    if (debouncedSearch) {
      // 300ms ํ›„ ๊ฒ€์ƒ‰ ์‹คํ–‰
      fetchResults(debouncedSearch);
    }
  }, [debouncedSearch]);
 
  return (
    <input
      value={search}
      onChange={e => setSearch(e.target.value)}
      placeholder="๊ฒ€์ƒ‰..."
    />
  );
}

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

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

function AutocompleteSearch() {
  const [query, setQuery] = useState('');
  const debouncedQuery = useDebounce(query, 500);
 
  useEffect(() => {
    if (debouncedQuery.length >= 2) {
      fetch(`/api/search?q=${debouncedQuery}`)
        .then(res => res.json())
        .then(setResults);
    }
  }, [debouncedQuery]);
 
  return <input onChange={e => setQuery(e.target.value)} />;
}

2. ์‹ค์‹œ๊ฐ„ ๊ฒ€์ฆ

function UsernameInput() {
  const [username, setUsername] = useState('');
  const debouncedUsername = useDebounce(username, 300);
  const [isAvailable, setIsAvailable] = useState(null);
 
  useEffect(() => {
    if (debouncedUsername) {
      checkUsername(debouncedUsername).then(setIsAvailable);
    }
  }, [debouncedUsername]);
 
  return (
    <div>
      <input onChange={e => setUsername(e.target.value)} />
      {isAvailable !== null && <span>{isAvailable ? 'โœ…' : 'โŒ'}</span>}
    </div>
  );
}

3. ์œˆ๋„์šฐ ๋ฆฌ์‚ฌ์ด์ฆˆ

function ResponsiveComponent() {
  const [size, setSize] = useState(window.innerWidth);
  const debouncedSize = useDebounce(size, 200);
 
  useEffect(() => {
    const handleResize = () => setSize(window.innerWidth);
    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);
 
  return <div>Width: {debouncedSize}px</div>;
}

API Reference

function useDebounce<T>(value: T, delay: number): T;

Parameters

value

๋””๋ฐ”์šด์Šคํ•  ๊ฐ’์ž…๋‹ˆ๋‹ค.

  • ํƒ€์ž…: T (์ œ๋„ค๋ฆญ)
  • ์˜ˆ์‹œ: string, number, object ๋“ฑ

delay

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

  • ํƒ€์ž…: number
  • ๊ถŒ์žฅ๊ฐ’: 300-500ms

๋ฐ˜ํ™˜๊ฐ’

  • ํƒ€์ž…: T (์ž…๋ ฅ๊ฐ’๊ณผ ๋™์ผํ•œ ํƒ€์ž…)
  • ์„ค๋ช…: ์ง€์—ฐ๋œ ๊ฐ’

๋™์ž‘ ๋ฐฉ์‹

์‚ฌ์šฉ์ž ์ž…๋ ฅ: a โ†’ ab โ†’ abc โ†’ abcd
              โ†“
useDebounce:  [300ms ๋Œ€๊ธฐ]
              โ†“
๊ฒฐ๊ณผ:         abcd (300ms ํ›„ ํ•œ ๋ฒˆ๋งŒ ์—…๋ฐ์ดํŠธ)

๋‚ด๋ถ€ ๊ตฌํ˜„:

export function useDebounce<T>(value: T, delay: number): T {
  const [debouncedValue, setDebouncedValue] = useState<T>(value);
 
  useEffect(() => {
    const timer = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);
 
    return () => clearTimeout(timer);
  }, [value, delay]);
 
  return debouncedValue;
}

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

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

  • ~0.3 KB (minified)
  • ~0.2 KB (gzipped)

๋ฉ”๋ชจ๋ฆฌ

  • useState 1๊ฐœ
  • setTimeout 1๊ฐœ (์ž๋™ cleanup)

๋ Œ๋”๋ง

  • value ๋ณ€๊ฒฝ ์‹œ๋งˆ๋‹ค ํƒ€์ด๋จธ ์žฌ์„ค์ •
  • delay ํ›„ 1ํšŒ ๋ฆฌ๋ Œ๋”๋ง

vs useDebouncedCallback

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

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

โš ๏ธ ์ดˆ๊ธฐ๊ฐ’ ์ฒ˜๋ฆฌ

// ์ดˆ๊ธฐ ๋ Œ๋”๋ง ์‹œ ์ฆ‰์‹œ ๋ฐ˜ํ™˜
const debouncedSearch = useDebounce('', 300);
console.log(debouncedSearch); // '' (์ฆ‰์‹œ)
 
// ์ดํ›„ ๋ณ€๊ฒฝ์€ ์ง€์—ฐ๋จ

โš ๏ธ delay ๋ณ€๊ฒฝ

// delay๊ฐ€ ์ž์ฃผ ๋ฐ”๋€Œ๋ฉด ํƒ€์ด๋จธ๊ฐ€ ๊ณ„์† ๋ฆฌ์…‹๋จ
const delay = Math.random() * 1000; // โŒ Bad
const debouncedValue = useDebounce(value, delay);
 
// delay๋Š” ๊ณ ์ •๊ฐ’ ์‚ฌ์šฉ
const debouncedValue = useDebounce(value, 300); // โœ… Good

TypeScript

// ํƒ€์ž… ์ž๋™ ์ถ”๋ก 
const search = 'hello';
const debouncedSearch = useDebounce(search, 300);
//    ^? string
 
const count = 42;
const debouncedCount = useDebounce(count, 300);
//    ^? number
 
// ๊ฐ์ฒด๋„ ๊ฐ€๋Šฅ
const filters = { category: 'tech', price: 100 };
const debouncedFilters = useDebounce(filters, 500);
//    ^? { category: string; price: number }

๊ด€๋ จ ๋ฌธ์„œ


์ฐธ๊ณ  ์ž๋ฃŒ