🌐 웹개발

Hooks

React Hooks

React Hooks는 함수 컴포넌트에서 상태 관리와 생명주기 기능을 사용할 수 있게 해주는 React 16.8에서 도입된 혁신적인 기능입니다. useState, useEffect, useContext 등을 통해 클래스 컴포넌트 없이도 완전한 React 애플리케이션을 구축할 수 있습니다.

📖 상세 설명

React Hooks는 2019년 React 16.8에서 정식 출시된 기능으로, 함수 컴포넌트에서 상태(state)와 생명주기(lifecycle) 기능을 사용할 수 있게 해줍니다. Hooks 이전에는 이러한 기능을 사용하려면 반드시 클래스 컴포넌트를 작성해야 했습니다. Hooks의 등장으로 함수 컴포넌트만으로도 완전한 기능을 갖춘 React 애플리케이션을 개발할 수 있게 되었습니다.

기본 Hooks에는 useState(상태 관리), useEffect(부수 효과 처리), useContext(컨텍스트 사용)가 있습니다. useState는 컴포넌트에 상태 변수를 추가하고, useEffect는 데이터 페칭, 구독, DOM 조작 등의 부수 효과를 처리합니다. useContext는 prop drilling 없이 컴포넌트 트리 전체에서 데이터를 공유할 수 있게 해줍니다.

추가 Hooks로는 useReducer(복잡한 상태 로직), useCallback(함수 메모이제이션), useMemo(값 메모이제이션), useRef(변경 가능한 ref 객체), useLayoutEffect(동기 DOM 업데이트), useImperativeHandle(ref 커스터마이징) 등이 있습니다. React 18에서는 useId, useTransition, useDeferredValue 등 새로운 Hooks가 추가되어 동시성 기능을 지원합니다.

커스텀 훅(Custom Hook)은 use로 시작하는 함수로, 여러 컴포넌트에서 재사용할 로직을 추출할 수 있습니다. useForm, useFetch, useLocalStorage 같은 커스텀 훅을 만들어 코드 중복을 줄이고 관심사를 분리할 수 있습니다. Hooks 규칙(최상위에서만 호출, React 함수 내에서만 호출)을 준수하면 예측 가능하고 일관된 동작을 보장받을 수 있습니다.

💻 코드 예제

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

// 커스텀 훅: 데이터 페칭
function useFetch<T>(url: string) {
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);

  useEffect(() => {
    const abortController = new AbortController();

    const fetchData = async () => {
      try {
        setLoading(true);
        const response = await fetch(url, {
          signal: abortController.signal
        });
        if (!response.ok) throw new Error('Network response was not ok');
        const result = await response.json();
        setData(result);
      } catch (err) {
        if (err.name !== 'AbortError') {
          setError(err as Error);
        }
      } finally {
        setLoading(false);
      }
    };

    fetchData();

    // 클린업: 컴포넌트 언마운트 시 요청 취소
    return () => abortController.abort();
  }, [url]);

  return { data, loading, error };
}

// 커스텀 훅: 로컬 스토리지
function useLocalStorage<T>(key: string, initialValue: T) {
  const [storedValue, setStoredValue] = useState<T>(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch {
      return initialValue;
    }
  });

  const setValue = useCallback((value: T | ((val: T) => T)) => {
    setStoredValue(prev => {
      const valueToStore = value instanceof Function ? value(prev) : value;
      window.localStorage.setItem(key, JSON.stringify(valueToStore));
      return valueToStore;
    });
  }, [key]);

  return [storedValue, setValue] as const;
}

// 메인 컴포넌트
function UserDashboard() {
  const [searchTerm, setSearchTerm] = useState('');
  const [theme, setTheme] = useLocalStorage('theme', 'dark');
  const { data: users, loading, error } = useFetch<User[]>('/api/users');

  // 검색 결과 메모이제이션
  const filteredUsers = useMemo(() => {
    if (!users) return [];
    return users.filter(user =>
      user.name.toLowerCase().includes(searchTerm.toLowerCase())
    );
  }, [users, searchTerm]);

  // 이벤트 핸들러 메모이제이션
  const handleSearch = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
    setSearchTerm(e.target.value);
  }, []);

  const toggleTheme = useCallback(() => {
    setTheme(prev => prev === 'dark' ? 'light' : 'dark');
  }, [setTheme]);

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div data-theme={theme}>
      <button onClick={toggleTheme}>Toggle Theme</button>
      <input
        type="text"
        value={searchTerm}
        onChange={handleSearch}
        placeholder="Search users..."
      />
      <ul>
        {filteredUsers.map(user => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </div>
  );
}

🗣️ 실무 대화 예시

💬 상황: 기술 면접에서 Hooks 사용 경험에 대해 질문하는 상황

면접관 "useEffect에서 cleanup 함수를 언제 사용해야 하나요? 실제 프로젝트에서 경험한 사례가 있나요?"
지원자 "cleanup 함수는 구독 해제, 타이머 정리, API 요청 취소 등에 사용합니다. 최근 프로젝트에서 WebSocket 연결을 다루며, 컴포넌트 언마운트 시 연결을 끊지 않아 메모리 누수가 발생했던 경험이 있습니다. cleanup에서 socket.disconnect()를 호출하여 해결했고, 이후로는 AbortController를 사용한 fetch 취소도 항상 적용하고 있습니다."
면접관 "useCallback과 useMemo의 차이점과, 언제 사용해야 하는지 설명해주세요."
지원자 "useCallback은 함수를, useMemo는 계산된 값을 메모이제이션합니다. useCallback은 자식 컴포넌트에 props로 전달되는 콜백 함수가 불필요하게 재생성되는 것을 방지할 때 유용합니다. useMemo는 비용이 큰 계산의 결과를 캐싱할 때 사용합니다. 다만 모든 곳에 적용하면 오히려 성능이 나빠질 수 있어서, React DevTools로 프로파일링 후 실제 병목이 있는 곳에만 적용합니다."

⚠️ 주의사항

🔗 관련 용어

📚 더 배우기