OI7MI

[React] useEffect() 와 Cleanup 함수 본문

프론트엔드/React

[React] useEffect() 와 Cleanup 함수

OI7MI 2026. 1. 7. 20:10

useEffect란?

실행 시점을 내 마음대로 결정하게 해주는 함수

리액트 컴포넌트는 state가 변할 때마다 전체 코드가 다시 실행(Re-render)되는 특징이 있다.

하지만 서버에서 데이터를 받아오는 API 호출처럼 

딱 한 번만 실행되어야 하거나, 특정 값이 변할 때만 실행되어야 하는 코드가 있다면?

이걸 내 마음대로 제어할 수 있게 해주는 게 바로 useEffect다.

useEffect(() => {
  // 1. 실행하고 싶은 코드 (첫 번째 인자)
}, [/* 2. 지켜볼 대상들 */]); // (두 번째 인자)
첫 번째 인자: 실행하고 싶은 코드(함수)
두 번째 인자: 리액트가 지켜볼 대상이 담긴 배열 (의존성 배열, [])

 

 

 

 

useEffect는 'Side Effect'를 위한 Hook이다

리액트에서 Side effect(부작용, 영향)란 렌더링 결과물과는 직접적인 상관이 없지만

컴포넌트 외부와 상호작용하는 작업들을 말한다

예시: 서버에서 데이터 받아오기(API), 수동으로 DOM 조작하기, 타이머 설정 등
특징: 이런 작업들은 렌더링이 다 끝난 뒤에 실행되어야 성능에 방해가 안 된다. 그래서 useEffect라는 Hook(훅)을 사용해서 실행 시점을 관리하는 것.

 

 

 

 

 

의존성 배열([])에 따른 마법

두 번째 인자인 배열을 어떻게 쓰느냐에 따라 실행 타이밍이 완전히 달라진다

 

컴포넌트가 처음 나타날 때 딱 한 번만 실행하고

그 뒤로는 어떤 일이 생겨도 실행하고 싶지 않을 때 두 번째 인자에 빈 배열을 넣어서 사용하고,

배열 안에 특정 변수를 넣어주면, 리액트가 그 값을 감시하다가 값이 바뀔 때만 코드를 다시 실행해준다.

 

  • 빈 배열일 때 []
동작: 컴포넌트가 처음 화면에 나타날 때(Mount) 딱 한 번만 실행하고 다시는 안 함.
용도: API 딱 한 번만 호출하고 싶을 때 사용. 리액트한테 "지켜볼 게 아무것도 없으니까 처음 말고는 다시 실행하지 마"라고 알려주는 것.
  • 특정 값이 있을 때 [keyword]
동작: 처음 나타날 때 + 값이 바뀔 때마다 실행.
용도: 특정 데이터가 변할 때마다 반응해야 하는 로직에 사용.

 

 

 

 

 

코드
import { useState, useEffect } from "react";

function App() {
  const [counter, setCounter] = useState(0);
  const [keyword, setKeyword] = useState("");

  const onClick = () => setCounter((prev) => prev + 1);
  const onChange = (e) => setKeyword(e.target.value);

  // 1. 처음 렌더링될 때 딱 한 번만 실행
  useEffect(() => {
    console.log("나는 한번만 실행돼요.");
  }, []);

  // 2. keyword가 변화할 때만 실행
  useEffect(() => {
    console.log(`나는 ${keyword} 가 변화할 때 실행돼요.`);
  }, [keyword]);

  // 3. counter가 변화할 때만 실행
  useEffect(() => {
    console.log(`나는 ${counter} 가 변화할 때 실행돼요.`);
  }, [counter]);

  // 4. 둘 중 하나라도 변화하면 실행
  useEffect(() => {
    console.log(`나는 ${counter}, ${keyword} 둘 다 변화할 때 실행돼요.`);
  }, [counter, keyword]);

  return (
    <div>
      <input type="text" placeholder="Search here..." value={keyword} onChange={onChange} />
      <h1>{counter}</h1>
      <button onClick={onClick}>click me</button>
    </div>
  );
}

 

 

 

 

 


useEffect 정리
의존성 배열 실행 타이밍
빈 배열 [] 컴포넌트가 처음 나타날 때(Mount) 딱 한 번
값이 있는 배열 [a] 처음 나타날 때 + a가 변할 때마다
여러 개 있는 배열 [a, b] 처음 나타날 때 + a 혹은 b가 변할 때마다

useEffect는 무분별한 코드 실행을 막아주는 방어막이자, 내가 원하는 시점을 정확히 조준하는 트리거다.


 

 

 

 

Cleanup 함수란?

useEffect는 컴포넌트가 처음 나타날 때 코드를 실행할 수 있게 해주지만

반대로 컴포넌트가 화면에서 사라질 때(Destroy/Unmount) 코드를 실행하는 방법도 제공한다.

이걸 Cleanup 함수라고 부른다.

 

Cleanup 함수는 useEffect 안에서 함수를 return 하면, 그 리턴된 함수는 컴포넌트가 파괴될 때 실행된다

아래 두 코드는 같다

// 1
function Hello() {
  useEffect(() => {
    console.log("created :)"); // 컴포넌트가 나타날 때 실행
    
    return () => console.log("destroyed :("); // 컴포넌트가 사라질 때 실행 (Cleanup)
  }, []);

  return <h1>Hello</h1>;
}

// 2
function Hello() {
  function byFn(){
    console.log("destroyed");
  }
  function hiFn(){
    console.log("created");
    return byFn;
  }
  useEffect(hiFn, []);

  return <h1>Hello</h1>;
}

 

 

 

 

2. 작동 원리: 왜 return을 할까?

리액트의 이펙트 라이프사이클은 아래와 같이 흘러간다

 

리렌더링/생성: 컴포넌트가 화면에 그려짐.
이펙트 실행: useEffect의 첫 번째 인자인 함수가 실행됨.클린업: 다음 이펙트가 실행되기 전이나, 컴포넌트가 파괴될 때 이전에 return 했던 함수를 실행해서 청소함.

 

 

 

 

3. 두 코드는 왜 결과가 같을까?

두 코드는 작성 방식만 다를 뿐, 자바스크립트의 함수 리턴 원리를 이용한 같은 로직

첫 번째: 익명 함수를 바로 리턴함. (가장 흔히 쓰는 방식)
두 번째: hiFn을 실행하면 byFn이라는 함수 자체를 결과값으로 리턴함.

결국 리액트 입장에서는 어쨌든 나한테 함수 하나를 리턴해줬네? 그럼 나중에 파괴될 때 이거 실행해 줄게! 가 되는 것.

 

 

 

 

4. 언제 사용할까?

컴포넌트가 사라지는데 왜 코드를 실행해야 할까? 보통 '정리'가 필요할 때 쓴다.

타이머 제거:
setTimeout이나 setInterval을 켰다면 사라질 때 꺼줘야 메모리 낭비가 없다.

이벤트 리스너 해제: 
윈도우 스크롤 감지 같은 걸 등록했다면 사라질 때 지워줘야 한다.

API 요청 취소:
데이터를 가져오다가 컴포넌트가 닫히면 더 이상 필요 없는 요청을 취소할 때 쓴다.

 

 

 

Cleanup 정리
  • Cleanup 함수: useEffect 안에서 return 하는 함수.
  • 실행 시점: 컴포넌트가 파괴(Unmount)될 때, 혹은 다음 이펙트가 실행되기 직전에 실행됨.
  • 의존성 배열이 []일 때: 컴포넌트가 아예 사라질 때 딱 한 번 실행됨.