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 이름은 항상
1. Hook은 항상 함수 컴포넌트 또는 커스텀 Hook의 최상위에서만 호출하세요.
2. 조건문, 반복문, 중첩 함수 안에서 Hook을 호출하면 안 됩니다.
3. Hook 이름은 항상
use로 시작해야 합니다.