Ch.22 React 심화 — 상태 관리 패턴

커스텀 훅 — 로직 재사용

커스텀 훅의 개념과 네이밍 규칙을 이해한다useFetch 커스텀 훅을 만들 수 있다실전에서 유용한 커스텀 훅 패턴을 안다

같은 fetch 코드를 10번 복사하고 있다면?

여러 컴포넌트에서 API를 호출할 때마다 useState, useEffect, loading, error 관련 코드를 반복 작성합니다.

반복되는 로직을 한 번만 작성하고 재사용하는 방법은?

커스텀 훅 — use로 시작하는 함수에 로직을 추출하면 어디서든 재사용할 수 있습니다.


article

핵심 내용

use로 시작하는 함수 = 커스텀 훅입니다

데이터, 로딩, 에러를 하나의 훅으로 묶습니다

// hooks/use-fetch.ts
import { useState, useEffect } from "react";

export function useFetch<T>(url: string) {
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    setLoading(true);
    setError(null);

    fetch(url)
      .then((res) => {
        if (!res.ok) throw new Error("요청 실패");
        return res.json();
      })
      .then(setData)
      .catch((e) => setError(e.message))
      .finally(() => setLoading(false));
  }, [url]);

  return { data, loading, error };
}
// 사용 — 3줄이면 API 호출 완료!
function UserProfile({ userId }: { userId: string }) {
  const { data, loading, error } = useFetch<User>(
    `/api/users/${userId}`
  );

  if (loading) return <p>로딩 중...</p>;
  if (error) return <p>에러: {error}</p>;
  if (!data) return null;

  return <h1>{data.name}</h1>;
}

// 다른 컴포넌트에서도 동일하게!
function ProductList() {
  const { data: products, loading } = useFetch<Product[]>(
    "/api/products"
  );
  // ...
}

자주 쓰는 패턴을 커스텀 훅으로 만들어두세요

// useLocalStorage — 로컬스토리지 동기화
function useLocalStorage<T>(key: string, initial: T) {
  const [value, setValue] = useState<T>(() => {
    const saved = localStorage.getItem(key);
    return saved ? JSON.parse(saved) : initial;
  });

  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);

  return [value, setValue] as const;
}

// 사용
const [theme, setTheme] = useLocalStorage("theme", "light");
// useDebounce — 연속 입력 방지
function useDebounce<T>(value: T, delay = 300): T {
  const [debounced, setDebounced] = useState(value);

  useEffect(() => {
    const timer = setTimeout(() => setDebounced(value), delay);
    return () => clearTimeout(timer);
  }, [value, delay]);

  return debounced;
}

// 사용 — 검색어 입력 시 300ms 후에만 API 호출
function Search() {
  const [query, setQuery] = useState("");
  const debouncedQuery = useDebounce(query, 300);

  const { data } = useFetch(
    `/api/search?q=${debouncedQuery}`
  );
  // ...
}
// useMediaQuery — 반응형 감지
function useMediaQuery(query: string): boolean {
  const [matches, setMatches] = useState(false);

  useEffect(() => {
    const media = window.matchMedia(query);
    setMatches(media.matches);

    const listener = (e: MediaQueryListEvent) =>
      setMatches(e.matches);
    media.addEventListener("change", listener);
    return () => media.removeEventListener("change", listener);
  }, [query]);

  return matches;
}

// 사용
const isMobile = useMediaQuery("(max-width: 768px)");
return isMobile ? <MobileNav /> : <DesktopNav />;

바이브 코더 팁 AI에게 "useWindowSize 커스텀 훅 만들어줘"라고 요청하세요. 결과 코드를 읽고 이해할 수 있으면, 수정도 자유자재입니다.

커스텀 훅의 필수 네이밍 규칙은?

커스텀 훅 안에서는 useState, useEffect 등 다른 훅을 사용할 수 없다

useDebounce의 주요 사용 사례는?

key

핵심 용어

📛

이름은 use로 시작

useUser, useFetch, useTheme 등

🧩

내부에서 훅 사용 가능

useState, useEffect 등 React 훅을 자유롭게 조합

📤

값을 반환

상태, 함수, 객체 등 필요한 것을 반환

♻️

재사용

여러 컴포넌트에서 import해서 사용

edit_note

정리 노트

커스텀 훅 핵심 정리

커스텀 훅 기본

커스텀 훅
use로 시작하는 함수로, 반복되는 로직을 재사용 가능하게 추출
규칙
반드시 use 접두사 사용, 최상위에서만 호출, 조건문 안에서 호출 금지
장점
컴포넌트를 깔끔하게 유지하고 로직 테스트가 쉬워짐

자주 쓰는 패턴

useLocalStorage
로컬스토리지 읽기/쓰기를 자동 동기화하는 훅
useDebounce
연속 입력 시 마지막 입력 후 일정 시간 뒤 실행하는 훅

같은 로직이 2개 이상의 컴포넌트에서 반복되면 커스텀 훅으로 추출할 시점입니다.

퀴즈와 인터랙션으로 더 깊이 학습하세요

play_circle인터랙티브 레슨 시작