본문 바로가기
프론트엔드 공부/React

React 기초: useEffect와 컴포넌트 생명주기 완벽 이해 + Quiz풀기

by liling2 2025. 11. 13.

 

 

컴포넌트 생명주기(Lifecycle)란?

 

리액트 컴포넌트는 태어나고 → 살고 → 사라지는 흐름을 가집니다.

이 세 순간마다 특정 코드를 실행하고 싶을 때 생명주기 메서드 또는 useEffect를 사용합니다.

단계 설명
Mount(마운트) 화면에 처음 등장하는 시점
Update(업데이트) state 또는 props가 변경되어 다시 렌더링되는 시점
Unmount(언마운트) 화면에서 사라지는 시점

 

 

useEffect를 사용하는 이유

 

리액트는 UI = state의 결과물이라는 철학이 있어서 “렌더링 과정”은 아주 순수하게 유지합니다.

 

  • state로 화면을 만들고
  • state가 바뀌면 다시 화면을 만들고

-> 이 과정에서는 부수효과(side-effect)를 절대 섞으면 안됩니다! 그래서 React가 만든 해결책이 바로 useEffect.

 


1. 렌더링과 부수효과(side-effects)를 분리하기 위해

console.log, fetch, scroll event, setTimeout, DOM 직접 변경 등… 이런 것들은 화면을 그리는 로직과 상관없는 부수효과라고합니다.

 

React 철학상
👉 렌더링 과정에는 순수한 계산만 있어야 합니다.

그래서 랜더링 과정에서 순수한 계산 그 외의 부수효과는 전부 useEffect 안에 넣어야 합니다.

 

이렇게 분리함:

function Component() {
  // 렌더링: 화면을 만드는 순수한 로직
  return <div>...</div>;
}

useEffect(() => {
  // 부수효과: 외부와 상호작용하는 모든 작업
});

2. 렌더링 이후에 실행해야 하는 작업을 처리하기 위해

리액트 렌더링 순서 : render → 화면 출력 → useEffect 실행


-> useEffect는 렌더링 이후에 실행됨.

 

그래서 이런 작업들을 useEffect에서 처리해야 합니다

  • API 호출(fetch, axios)
  • DOM 크기 측정
  • document.title 변경
  • 이벤트 리스너 등록(scroll, resize)
  • 타이머(setInterval)

이런 것들은 “화면이 그려지고 난 뒤”에 해야 하기 때문입니다.!


3. 특정 값이 변경될 때만 실행되는 동작을 만들기 위해

 

예를 들어:

  • count 값이 변할 때 API 요청 다시 보내기
  • searchTerm이 바뀔 때마다 검색 결과 최신화
  • props로 받은 userId가 바뀌면 유저 정보 새로 불러오기

이런 동작은 특정 값 변화에 따라 제어해야 합니다. useEffect의 [의존성 배열]이 이걸 맡아요!

useEffect(() => {
  console.log("count 변경됨");
}, [count]); // count가 변할 때만 실행

-> [의존성 배열] 덕분에 원하는 시점에만 원하는 effect를 실행할 수 있음.


4. 컴포넌트가 사라질 때 정리(cleanup)하기 위해

 

이건 특히 “실무에서 매우 중요한 이유”입니다.

 

컴포넌트가 사라질 때:

  • 이벤트 리스너 제거
  • setInterval 정리
  • 서버 구독(WebSocket) 해제
  • 메모리 누수 방지

이런 정리(cleaning) 작업이 반드시 필요해요. 그걸 useEffect 안에서 이렇게 처리합니다.

useEffect(() => {
  window.addEventListener("scroll", onScroll);

  return () => {
    window.removeEventListener("scroll", onScroll);
  };
}, []);

-> return 내부의 함수가 바로 정리 함수입니다. 이 cleanup 기능은 class 컴포넌트의 componentWillUnmount 역할과 동일해요

 

 간단하게 말하자면 useEffect

렌더링이 끝난 뒤 부수효과를 안전하게 실행하고, 생명주기를 제어하는 도구입니다.!

 


Class 컴포넌트의 생명주기 메서드


리액트의 전통적인 방식은 클래스형 컴포넌트를 사용하는 것이었습니다.
현재 대부분의 프로젝트는 함수형 컴포넌트를 쓰지만, 생명주기 개념을 이해하는 데 도움되므로 가장 핵심적인 3가지 메서드를 알아봅시다.

 

componentDidMount() — “마운트 직후 실행”

componentDidMount() {
  console.log("처음 화면에 등장!");
  // API 호출, 이벤트 등록, DOM 접근 등
}

 

componentDidUpdate(prevProps, prevState) — “업데이트 직후 실행”

componentDidUpdate(prevProps, prevState) {
  console.log("업데이트됨!");
}

 

componentWillUnmount() — “언마운트 직전 실행 (정리)"

componentWillUnmount() {
  console.log("사라지기 직전!");
  // 타이머 제거, 리스너 제거 등 정리 작업
}

 

 


함수형 컴포넌트에서의 생명주기 — useEffect


함수형 컴포넌트는 생명주기 메서드가 없습니다.
대신 useEffect가 이 모든 역할을 통합해서 담당합니다.

 

 

useEffect 기본 형태

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

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

 

 

1. 컴포넌트가 최초로 렌더링 될 때에만 조작을 하고싶다. !

useEffect(() => {
	console.log("맨 처음 렌더링 될 때");
}, []);

-> 두 번째 인자로 오는 [의존선 배열]이 비어있는 상태로 코드가 작성됩니다.

 

2. 컴포넌트가 리렌더링 될 때 조작하고 싶다. !

useEffect(() => {
	console.log("리렌더링...");
},);

-> 두 번째 인자로 오는 [의존성 배열] 없이 코드가 작성됩니다.

 

3. 특정 상태값이 변할 때에만 조작하고 싶다. !

useEffect(() => {
	console.log("counter의 값이 변할 때");
}, [conter]);



useEffect(() => {
	console.log("counter2의 값이 변할 때");
}, [conter2]);

-> 두 번째 인자로 오는 [의존성 배열]자리에 들어온 counter와 counter2는 useState명을 가져온겁니다 !

 

4. 컴포넌트가 최종적으로 언마운트 될 때 조작하고 싶다. !

useEffect(() => {
	return () => {
		console.log("컴포넌트 언마운트");
    };
}, []);

-> useEffect() 함수 안에 return 안에 작성되면 언마운트 될 떄 조작이 가능합니다 ! 

 


 

 

역할 클래스형 컴포넌트 함수형 컴포넌트(useEffect)
마운트 시 한 번 실행 componentDidMount useEffect(() => {}, [])
특정 값 업데이트 시 실행 componentDidUpdate useEffect(() => {}, [value])
언마운트 직전 실행 componentWillUnmount return () => { ... }

초보자 입장에서는 이렇게 외워도 됩니다.

빈 배열 = 처음 한 번
배열에 값 넣기 = 그 값이 변할 때마다
cleanup = 사라질 때

 

 


예제로 이해하는 useEffect

 

 

 

✔ 예제: count 값이 바뀔 때마다 페이지 제목 변경

count 변경 → 재렌더링 → useEffect 실행 → 브라우저 제목 변경

import { useState, useEffect } from "react";

function TitleCounter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `클릭 횟수: ${count}`;
  }, [count]); // count가 바뀔 때마다 실행

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

export default TitleCounter;

 


조금 더 응용된 내용 : 이벤트 리스너 + useEffect + cleanup 쉽게 이해하기

왜 cleanup(정리)이 필요할까?


예를 들어 컴포넌트가 이렇게 있다고 가정해볼게요~

  • 컴포넌트가 처음 보일 때 → window.addEventListener("scroll") 등록
  • 컴포넌트가 사라질 때 → 스크롤 이벤트 제거해야 함

왜냐하면…

컴포넌트는 화면에서 사라졌는데 리스너는 계속 남아 있으면
“메모리 낭비 + 이상한 오류"가 생길 수 있기 때문!

그래서 useEffect에서 이렇게 작성합니다.

 

기본 패턴 : 이 패턴이 “언마운트 시 정리하는” 정석 패턴입니다.

useEffect(() => {
  // 1. 등록
  window.addEventListener("scroll", handleScroll);

  // 2. 언마운트되기 직전 정리
  return () => {
    window.removeEventListener("scroll", handleScroll);
  };
}, []);
  • [의존성 배열] : 마운트 시 한 번만 등록됨, 언마운트 시 한 번만 정리됨
  • 여기서 return() 부분이 cleanup 함수입니다.

 

 

 

 

 


 

🐣 Mini Quiz 🐣

 

“아~ 이렇게 이해하면 되는구나!” 하고 정리할 수 있도록 useEffect 핵심 개념을 완전히 파악할 수 있는 4지선다형 퀴즈를 가져왔어요!
정답은 맨 댓글에 남겨둘게요 ^_^!

퀴즈 풀어보고 댓글남겨주세요~

 


 

🧩 React useEffect 완전 정복! 4지선다형 퀴즈 10문제 🧩


 

 

Q1. useEffect(() => {}, []); 의 실행 시점은?

 

A. 모든 렌더링마다 실행된다
B. 컴포넌트가 처음 마운트될 때 단 한 번 실행된다
C. state가 바뀔 때만 실행된다
D. 컴포넌트가 사라질 때만 실행된다


Q2. 의존성 배열(dependency array)에 count를 넣으면 언제 실행될까?

useEffect(() => { console.log(count); }, [count]);

 

A. count가 바뀔 때에만 실행된다
B. 렌더링마다 실행된다
C. 컴포넌트가 처음 렌더될 때만 실행된다
D. 부모 컴포넌트가 렌더링될 때마다 실행된다


Q3. 언마운트(컴포넌트 사라짐) 시 어떤 코드가 실행될까?

A. useEffect의 콜백 함수
B. 의존성 배열 내부의 변수
C. useEffect의 return() 내부 함수
D. 아무 것도 실행되지 않는다


Q4. 다음 중 useEffect를 사용하는 이유로 ‘올바른 것’은?

A. 렌더링 전에 실행하기 위해서
B. 비동기 작업을 지연시키기 위해서
C. 렌더링 이후 부수효과(side-effect)를 실행하기 위해서
D. return문을 대신하기 위해서


Q5. 다음 중 “마운트 시 한 번만 실행되는” 패턴은?

A. useEffect(() => {}, [value])
B. useEffect(() => {})
C. useEffect(() => {}, [])
D. useEffect(() => { return cleanup })


Q6. 이벤트 리스너 등록 → 언마운트 시 제거하려면 어떤 패턴을 사용해야 할까?

A. 의존성 배열 없이 useEffect 실행
B. 빈 의존성 배열 + cleanup 함수
C. setTimeout 내부에 이벤트 리스너 제거
D. useState로 이벤트 제거


Q7. 클래스형 컴포넌트의 componentWillUnmount에 해당하는 부분은?

A. useEffect의 첫 번째 인자
B. useEffect의 두 번째 인자
C. useEffect의 return() 내부 함수
D. 렌더 함수 내부 코드


Q8. 다음 useEffect가 무한 렌더링을 일으키는 이유는?

useEffect(() => {
  setCount(count + 1);
});

 

A. 의존성 배열이 비어 있어서
B. 렌더링마다 setCount가 실행되기 때문에
C. useState가 없기 때문에
D. cleanup 함수가 없기 때문에


Q9. 다음 중 “업데이트 시에만 실행되는” useEffect는?

A. useEffect(() => {}, [])
B. useEffect(() => {})
C. useEffect(() => {}, [value])
D. useEffect(() => { return () => {} })


Q10. 의존성 배열에 아무것도 넣지 않는 경우(useEffect(() => {})) 어떤 일이 일어날까?

A. 단 한 번 실행된다
B. 렌더링마다 매번 실행된다
C. 언마운트될 때 실행된다
D. cleanup 함수가 자동으로 추가된다