React의 useEffect에서 Cleanup(정리) 함수의 역할

nara_lee·2025년 3월 16일
0

React Hook을 공부하다가 useEffect 의 cleanup function에 대해 궁금해져서 찾아보았다.

React에서 useEffectcleanup 함수메모리 누수를 방지하고, 컴포넌트가 사라지거나(unmount), useEffect가 다시 실행되기 전에 부작용(side effects)을 정리하는 역할을 합니다.


1. Cleanup 함수는 언제 실행될까?

  • 컴포넌트가 언마운트(Unmount)될 때
    useEffect 내부에서 이벤트 리스너를 추가하거나, 타이머를 설정하는 경우, 컴포넌트가 제거될 때 정리(cleanup)를 해야 함.
  • useEffect의 의존성(dependency)이 변경될 때
    → 의존성 배열([]) 안의 값이 바뀔 경우, useEffect가 다시 실행되기 전에 기존의 실행된 효과를 정리(cleanup)한 후 새로운 useEffect를 실행함.

2. Cleanup 함수의 기본 문법

React의 useEffect에서 return 문을 사용하여 cleanup 함수를 정의할 수 있습니다:

useEffect(() => {
  console.log("Effect 실행됨!");

  return () => {
    console.log("Cleanup 함수 실행됨!");
  };
}, []); // 의존성 배열이 비어 있으므로 컴포넌트가 언마운트될 때만 실행됨

3. 이벤트 리스너 정리 (실제 예제)

예제 1: 윈도우 크기 변경 감지

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

const WindowResize = () => {
  const [width, setWidth] = useState(window.innerWidth);

  useEffect(() => {
    const handleResize = () => {
      setWidth(window.innerWidth);
    };

    window.addEventListener("resize", handleResize);

    return () => {
      window.removeEventListener("resize", handleResize); // Cleanup 함수 실행
    };
  }, []); // 의존성 배열이 없으므로 처음 마운트될 때만 실행됨

  return <h1>현재 창 너비: {width}px</h1>;
};

export default WindowResize;

🔥 왜 cleanup 함수가 필요할까?

  • window.addEventListener("resize", handleResize)를 추가했지만 컴포넌트가 언마운트될 때 이벤트 리스너를 제거하지 않으면, 메모리 누수가 발생할 수 있음.
  • Cleanup 함수가 없으면 이벤트 리스너가 계속 실행됨, 심한 경우 앱의 성능을 저하시킬 수도 있음.

Eventlisner 란?

// eventlistner 예시
element.addEventListener("click", function(){ alert("Hello World!"); });

4. Cleanup 함수가 필요한 이유

A. 메모리 누수 방지

setInterval, setTimeout 같은 타이머를 사용할 때, cleanup을 하지 않으면 컴포넌트가 사라진 후에도 실행됨 → 이를 방지하려면 정리 필요!

❌ 메모리 누수 예제 (Cleanup 없음)

useEffect(() => {
  setInterval(() => {
    console.log("이 코드는 계속 실행됨...");
  }, 1000);
}, []); // 언마운트 시에도 계속 실행됨

✅ 올바른 Cleanup 사용 (메모리 누수 방지)

useEffect(() => {
  const intervalId = setInterval(() => {
    console.log("이 코드는 정리됩니다!");
  }, 1000);

  return () => clearInterval(intervalId); // Cleanup: 타이머 해제
}, []);

B. 불필요한 API 요청 방지

의존성 값(query)이 바뀔 때마다 API 요청을 보내면, 이전 요청이 끝나기도 전에 새로운 요청이 실행될 수 있음 → Cleanup 함수를 이용해 이전 요청을 취소해야 함.

❌ 잘못된 코드 (불필요한 API 요청 발생)

useEffect(() => {
  fetch(`https://api.example.com/data?query=${query}`)
    .then(response => response.json())
    .then(data => console.log(data));
}, [query]); // query가 변경될 때마다 실행됨 (이전 요청이 완료되지 않아도 새 요청이 실행됨)

✅ 올바른 코드 (AbortController로 이전 요청 취소)

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

  fetch(`https://api.example.com/data?query=${query}`, { signal: controller.signal })
    .then(response => response.json())
    .then(data => console.log(data))
    .catch(err => {
      if (err.name === "AbortError") {
        console.log("이전 요청 취소됨!");
      }
    });

  return () => controller.abort(); // Cleanup: 이전 요청 취소
}, [query]);
  • controller.abort()를 실행하면 이전 요청이 자동으로 취소됨.
  • 빠르게 query 값이 변경되더라도 불필요한 API 요청이 줄어들어 성능이 개선됨.

5. Cleanup 함수가 필요 없는 경우

  • useEffect에서 단순히 데이터를 가져오거나 로깅하는 경우에는 cleanup 함수가 필요 없음.
  • 예제:
    useEffect(() => {
      console.log("컴포넌트가 마운트됨!");
    }, []);
    • 부작용 없이 실행되는 코드이므로 cleanup이 필요 없음.

6. 정리

Cleanup 함수(return 문 사용)의 역할

  • useEffect 내부에서 실행된 부작용을 정리하는 함수.
  • 컴포넌트가 언마운트될 때 실행됨.
  • useEffect가 다시 실행되기 전에 실행됨 (의존성 변경 시).

Cleanup이 필요한 경우

  • 이벤트 리스너 추가 (window.addEventListener)
  • 타이머 설정 (setTimeout, setInterval)
  • API 요청 처리 (이전 요청 취소)
  • WebSocket 연결 유지

Cleanup이 필요하지 않은 경우

  • 단순한 데이터 조회 (console.log 찍기 등)
  • 한 번만 실행되는 코드 ([]를 의존성 배열로 넣을 때)

🚀 결론: Cleanup을 잘 활용하면 React 컴포넌트의 성능이 좋아지고 메모리 누수를 방지할 수 있다!

0개의 댓글