ReactReact 기초강의 10개 중 4번째난이도 기초

useEffect — 부수 효과와 생명주기 처리

발행 읽는 시간 약 1 분

  • React
  • useEffect
  • 생명주기
  • 부수효과
  • 클린업

강의 소개

useEffect로 API 호출, 구독, 타이머 등 부수 효과를 처리하는 방법을 배웁니다. 의존성 배열의 의미와 클린업 함수의 역할도 이해합니다.

부수 효과란?#

렌더링 자체와 무관한 작업들입니다:

  • API 데이터 불러오기
  • 이벤트 리스너 등록/해제
  • 타이머 설정/해제
  • DOM 직접 조작

useEffect 기본#

TSX
import { useEffect, useState } from "react";

function UserProfile({ userId }: { userId: number }) {
    const [user, setUser] = useState<User | null>(null);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
        // userId가 바뀔 때마다 실행
        async function fetchUser() {
            setLoading(true);
            const response = await fetch(`/api/users/${userId}`);
            const data = await response.json();
            setUser(data);
            setLoading(false);
        }

        fetchUser();
    }, [userId]);  // 의존성 배열

    if (loading) return <div>로딩 중...</div>;
    if (!user) return <div>사용자 없음</div>;
    return <div>{user.name}</div>;
}

의존성 배열 규칙#

Diagram · Mermaid
TSX
// 마운트 시 1번만 (componentDidMount)
useEffect(() => {
    initializeSomething();
}, []);

// count가 바뀔 때마다
useEffect(() => {
    document.title = `카운트: ${count}`;
}, [count]);

// 매 렌더링마다 (보통 의도하지 않음)
useEffect(() => {
    console.log("렌더링됨");
});

클린업 함수#

컴포넌트가 사라질 때(언마운트) 또는 다음 effect 실행 전에 정리합니다.

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

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

        // 클린업: 컴포넌트 언마운트 시 실행
        return () => clearInterval(interval);
    }, []);

    return <div>경과: {seconds}초</div>;
}
TSX
// 이벤트 리스너 클린업
useEffect(() => {
    const handleResize = () => setWidth(window.innerWidth);
    window.addEventListener("resize", handleResize);
    return () => window.removeEventListener("resize", handleResize);
}, []);

실전: 데이터 페칭 패턴#

TSX
interface FetchState<T> {
    data: T | null;
    loading: boolean;
    error: Error | null;
}

function useApi<T>(url: string) {
    const [state, setState] = useState<FetchState<T>>({
        data: null,
        loading: true,
        error: null,
    });

    useEffect(() => {
        let cancelled = false;  // 경쟁 조건 방지

        setState({ data: null, loading: true, error: null });

        fetch(url)
            .then(r => r.json())
            .then(data => {
                if (!cancelled) setState({ data, loading: false, error: null });
            })
            .catch(error => {
                if (!cancelled) setState({ data: null, loading: false, error });
            });

        return () => { cancelled = true; };
    }, [url]);

    return state;
}

// 사용
function UserList() {
    const { data: users, loading, error } = useApi<User[]>("/api/users");

    if (loading) return <div>로딩 중...</div>;
    if (error) return <div>오류: {error.message}</div>;
    return <ul>{users?.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}

useEffect 주의사항#

TSX
// ❌ 무한 루프: 매 렌더링마다 객체 생성 → effect 재실행
useEffect(() => {
    fetchData(options);
}, [options]);  // options = { page: 1 } (매번 새 객체)

// ✅ 원시값을 의존성으로
useEffect(() => {
    fetchData({ page });
}, [page]);

// ❌ effect 안에서 async 직접 사용 불가
useEffect(async () => { ... });  // 반환값이 함수여야 함

// ✅ 내부에서 async 함수 정의
useEffect(() => {
    async function load() { ... }
    load();
}, []);

정리#

패턴의존성 배열실행 시점
초기화[]마운트 1회
값 감시[value]마운트 + value 변경
항상 실행없음매 렌더링
클린업-다음 effect 전 + 언마운트

다음 편에서는 조건부 렌더링과 리스트 — 동적으로 UI를 그리는 다양한 패턴을 배웁니다.

Follow · Ask · Work

이 글이 도움이 됐다면

짧은 작업 기록과 데모는 인스타그램에 올립니다. 만들고 싶은 것이 있다면 프로젝트 의뢰나 협업 제안도 편하게 보내 주세요.

글쓴이

파란여우

직접 만든 AI 앱·제품의 개발·배포를 공개하는 실전형 테크 크리에이터

파란여우 소개 Instagram @bluefox._.hi

이어서 보기

만들고 싶은 것이 있다면 프로젝트 의뢰로 알려 주세요.