🪝
EPISODE 05
use* · useFetch · useLocalStorage · useDebounce
커스텀 훅 (Custom Hook)
use로 시작하는 커스텀 훅의 개념과 ESLint 규칙, 공통 로직 추출의 이점, 그리고 useFetch/useLocalStorage/useDebounce 직접 구현을 다룹니다.
Reactcustom hookuseFetch
소요 시간
⏱ 45분
난이도
📊 중급
선수 조건
🎯 react-04
결과물
재사용 가능한 useFetch/useLocalStorage/useDebounce
이 강의에서 배우는 것
- 1커스텀 훅이 use로 시작해야 하는 이유를 안다
- 2중복 로직을 커스텀 훅으로 추출한다
- 3useFetch — Promise/AbortController 패턴을 적용한다
- 4useLocalStorage — JSON 직렬화·복원을 처리한다
- 5useDebounce — setTimeout/clearTimeout 패턴을 안다
1. 커스텀 훅이란
jsx
// ✅ use로 시작
function useWindowSize() { ... }
function useFetch(url) { ... }
// ❌ use로 시작하지 않으면 훅 규칙 검사 X
function getWindowSize() { ... }- React는 use로 시작하는 함수에서만 훅 규칙 검사
- ESLint react-hooks/rules-of-hooks가 정상 동작
- 다른 개발자가 즉시 훅임을 인식
2. 로직 추출의 이점
추출 전 — 중복
jsx
function ComponentA() {
const [data, setData] = useState(null);
const [isLoading, setIsLoading] = useState(true);
// ... 같은 패턴 반복
}추출 후 — 한 줄로 해결
jsx
function ComponentA() {
const { data, isLoading, error } = useFetch('/api/users');
}
function ComponentB() {
const { data, isLoading, error } = useFetch('/api/products');
}3. useFetch
jsx
function useFetch(url) {
const [data, setData] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
if (!url) return;
const controller = new AbortController();
setIsLoading(true);
fetch(url, { signal: controller.signal })
.then(r => { if (!r.ok) throw new Error('실패'); return r.json(); })
.then(setData)
.catch(err => { if (err.name !== 'AbortError') setError(err.message); })
.finally(() => setIsLoading(false));
return () => controller.abort();
}, [url]);
return { data, isLoading, error };
}4. useLocalStorage
jsx
function useLocalStorage(key, initialValue) {
const [storedValue, setStoredValue] = useState(() => {
try {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch {
return initialValue;
}
});
const setValue = (value) => {
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
localStorage.setItem(key, JSON.stringify(valueToStore));
};
return [storedValue, setValue];
}5. useDebounce
jsx
function useDebounce(value, delay = 500) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timer = setTimeout(() => setDebouncedValue(value), delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debouncedValue;
}