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
| ํน์ง | useDebounce | useDebouncedCallback |
|---|---|---|
| ์ฉ๋ | ๊ฐ ๋๋ฐ์ด์ค | ํจ์ ๋๋ฐ์ด์ค |
| ์ฌ์ฉ | ๊ฒ์์ด, ์ ๋ ฅ๊ฐ | ์ด๋ฒคํธ ํธ๋ค๋ฌ |
| ๋ฐํ | ๊ฐ | ํจ์ |
| ์ต์ | โ | 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); // โ
GoodTypeScript
// ํ์
์๋ ์ถ๋ก
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 }๊ด๋ จ ๋ฌธ์
- useDebouncedCallback - ํจ์ ๋๋ฐ์ด์ค
- useThrottle - ์ฐ๋กํ๋ง (๊ฐ๋ฐ ์์ )