React 16.8에서 도입된 Hooks는 함수형 컴포넌트에서 상태 관리와 생명주기를 다룰 수 있게 해주었습니다. 클래스 컴포넌트 없이도 강력한 기능을 구현할 수 있는 Hooks를 완전히 정복해봅시다.

useState — 상태 관리

가장 기본적인 Hook으로, 컴포넌트의 상태를 선언하고 업데이트합니다.

import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);
  const [user, setUser] = useState({ name: '', age: 0 });

  // 이전 상태 기반으로 업데이트 (권장)
  const increment = () => setCount(prev => prev + 1);

  // 객체 상태 업데이트 — 스프레드로 병합
  const updateName = (name) => setUser(prev => ({ ...prev, name }));

  return (
    <div>
      <p>카운트: {count}</p>
      <button onClick={increment}>+1</button>
    </div>
  );
}

useEffect — 사이드 이펙트

데이터 페칭, 구독, DOM 조작 등 렌더링 외부의 작업을 처리합니다.

import { useState, useEffect } from 'react';

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    // 의존성 배열의 값이 바뀔 때마다 실행
    let cancelled = false;

    fetch(`/api/users/${userId}`)
      .then(res => res.json())
      .then(data => {
        if (!cancelled) setUser(data);
      });

    // cleanup — 컴포넌트 언마운트 또는 재실행 전에 호출
    return () => { cancelled = true; };
  }, [userId]); // userId가 바뀔 때만 재실행

  if (!user) return <p>로딩 중...</p>;
  return <p>{user.name}</p>;
}

useCallback & useMemo — 성능 최적화

import { useState, useCallback, useMemo } from 'react';

function ExpensiveList({ items, onSelect }) {
  // 함수 메모이제이션 — 의존성이 바뀔 때만 새 함수 생성
  const handleClick = useCallback((id) => {
    onSelect(id);
  }, [onSelect]);

  // 값 메모이제이션 — 비용이 큰 계산 결과를 캐싱
  const sortedItems = useMemo(() => {
    return [...items].sort((a, b) => a.price - b.price);
  }, [items]);

  return (
    <ul>
      {sortedItems.map(item => (
        <li key={item.id} onClick={() => handleClick(item.id)}>
          {item.name} — {item.price}원
        </li>
      ))}
    </ul>
  );
}

useRef — DOM 참조 & 값 유지

import { useRef, useEffect } from 'react';

function AutoFocusInput() {
  const inputRef = useRef(null);
  const renderCount = useRef(0); // 렌더링과 무관하게 값 유지

  useEffect(() => {
    inputRef.current.focus(); // DOM 직접 접근
    renderCount.current += 1;
  });

  return <input ref={inputRef} placeholder="자동 포커스" />;
}

커스텀 Hook — 로직 재사용

반복되는 상태 로직을 커스텀 Hook으로 추출해 재사용합니다.

// useFetch.js
function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    setLoading(true);
    fetch(url)
      .then(res => res.json())
      .then(setData)
      .catch(setError)
      .finally(() => setLoading(false));
  }, [url]);

  return { data, loading, error };
}

// 사용
function Posts() {
  const { data, loading, error } = useFetch('/api/posts');
  if (loading) return <p>로딩 중...</p>;
  if (error) return <p>오류 발생</p>;
  return <ul>{data.map(p => <li key={p.id}>{p.title}</li>)}</ul>;
}
Hooks 규칙
1. Hook은 항상 함수 컴포넌트 또는 커스텀 Hook의 최상위에서만 호출하세요.
2. 조건문, 반복문, 중첩 함수 안에서 Hook을 호출하면 안 됩니다.
3. Hook 이름은 항상 use로 시작해야 합니다.