3 분 소요

개요

useEffect는 컴포넌트가 렌더링된 후 사이드 이펙트(Side Effect)를 실행하기 위한 Hook입니다. 사이드 이펙트란 API 호출, DOM 직접 조작, 타이머 설정, 이벤트 구독 등 렌더링 외부의 작업을 말합니다.


기본 구조

import { useEffect } from "react";

useEffect(() => {
  // 실행할 코드

  return () => {
    // (선택) 정리(cleanup) 코드
  };
}, [/* 의존성 배열 */]);


의존성 배열

의존성 배열에 따라 실행 시점이 달라집니다.

import { useState, useEffect } from "react";

function App() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState("Alice");

  // 1. 의존성 배열 생략: 렌더링마다 실행
  useEffect(() => {
    console.log("매 렌더링마다 실행");
  });

  // 2. 빈 배열: 마운트(최초 1회)에만 실행
  useEffect(() => {
    console.log("최초 1회만 실행");
  }, []);

  // 3. 특정 값: 해당 값이 변경될 때마다 실행
  useEffect(() => {
    console.log("count가 변경됨:", count);
  }, [count]);

  // 4. 여러 값: 하나라도 변경되면 실행
  useEffect(() => {
    console.log("count 또는 name이 변경됨");
  }, [count, name]);

  return (
    <button onClick={() => setCount(count + 1)}>count: {count}</button>
  );
}


API 데이터 가져오기

import { useState, useEffect } from "react";

interface Post {
  id: number;
  title: string;
  body: string;
}

function App() {
  const [posts, setPosts] = useState<Post[]>([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    // 비동기 함수는 useEffect 안에서 정의 후 호출
    const fetchPosts = async () => {
      try {
        setLoading(true);
        const res = await fetch("https://jsonplaceholder.typicode.com/posts?_limit=5");
        if (!res.ok) throw new Error("데이터를 불러오지 못했습니다");
        const data: Post[] = await res.json();
        setPosts(data);
      } catch (err) {
        setError(err instanceof Error ? err.message : "오류 발생");
      } finally {
        setLoading(false);
      }
    };

    fetchPosts();
  }, []);  // 마운트시 1회 실행

  if (loading) return <p>로딩 중...</p>;
  if (error) return <p>오류: {error}</p>;

  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}


의존성에 따른 재호출

import { useState, useEffect } from "react";

function App() {
  const [userId, setUserId] = useState(1);
  const [user, setUser] = useState<{ name: string; email: string } | null>(null);

  useEffect(() => {
    const fetchUser = async () => {
      const res = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`);
      const data = await res.json();
      setUser(data);
    };

    fetchUser();
  }, [userId]);  // userId가 변경될 때마다 재호출

  return (
    <div>
      <div>
        {[1, 2, 3, 4, 5].map((id) => (
          <button key={id} onClick={() => setUserId(id)}>
            사용자 {id}
          </button>
        ))}
      </div>
      {user && (
        <div>
          <p>이름: {user.name}</p>
          <p>이메일: {user.email}</p>
        </div>
      )}
    </div>
  );
}


Cleanup (정리)

컴포넌트가 화면에서 사라질 때(언마운트) 또는 effect가 다시 실행되기 전에 정리 작업을 수행합니다.

import { useState, useEffect } from "react";

function Timer() {
  const [seconds, setSeconds] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => {
      setSeconds((prev) => prev + 1);
    }, 1000);

    // cleanup: 컴포넌트가 사라질 때 타이머 정리
    return () => {
      clearInterval(interval);   // 메모리 누수 방지
      console.log("타이머 정리!");
    };
  }, []);

  return <p>경과: {seconds}</p>;
}

// 이벤트 구독 정리 예시
useEffect(() => {
  const handleResize = () => console.log(window.innerWidth);
  window.addEventListener("resize", handleResize);

  return () => {
    window.removeEventListener("resize", handleResize);  // 구독 해제
  };
}, []);


흔한 실수

useEffect 안에서 State 무한 업데이트

// ❌ 무한 루프: count를 의존성에 넣고 count를 업데이트
useEffect(() => {
  setCount(count + 1);
}, [count]);   // count 변경 → effect 실행 → count 변경 → ...

// ✅ 함수형 업데이트로 의존성 제거
useEffect(() => {
  setCount((prev) => prev + 1);
}, []);

빈 배열인데 외부 변수 참조

const [userId, setUserId] = useState(1);

// ❌ userId가 변해도 effect는 최초만 실행, 항상 userId=1로 요청
useEffect(() => {
  fetch(`/api/users/${userId}`);
}, []);

// ✅ userId 의존성 추가
useEffect(() => {
  fetch(`/api/users/${userId}`);
}, [userId]);


useEffect vs 이벤트 핸들러

상황 사용 방법
버튼 클릭에 반응 이벤트 핸들러 (onClick)
컴포넌트 표시 시 데이터 로드 useEffect
특정 값 변경 감지 useEffect + 의존성 배열
외부 시스템 연동(웹소켓, 타이머) useEffect + cleanup


관련 링크