Front-End/React

React의 useEffect 훅과 사이드 이펙트 알아보기

jakejeong 2025. 2. 8. 09:36

React의 useEffect와 사이드 이펙트 이해하기

React 애플리케이션을 개발하다 보면 컴포넌트 내에서 데이터 페칭, DOM 조작, 구독(subscription) 설정 등 다양한 사이드 이펙트를 다뤄야 할 때가 있습니다. 오늘은 React의 useEffect 훅을 통해 이러한 사이드 이펙트를 어떻게 효과적으로 관리할 수 있는지 알아보겠습니다.

사이드 이펙트란?

사이드 이펙트는 컴포넌트의 렌더링 과정에서 발생하는 순수하지 않은 동작들을 의미합니다. 예를 들면:

  • API 호출
  • DOM 직접 조작
  • 브라우저 API (localStorage, setTimeout 등) 사용
  • 구독 설정 및 해제

이러한 작업들은 컴포넌트의 렌더링에 직접적인 영향을 미치지 않지만 애플리케이션의 동작에 필수적인 요소들입니다.

useEffect 개요와 기본 사용법

useEffect는 리액트에서 이러한 사이드 이펙트를 관리할 수 있는 훅입니다.
useEffect는 다음과 같은 형태로 사용됩니다:

useEffect(() => {
  // 실행할 사이드 이펙트 코드

  return () => {
    // 클린업 함수 (선택적)
  };
}, [/* 의존성 배열 */]);

의존성 배열에 관찰할 상태를 넣고, 해당 상태가 변하면 사이드 이펙트 코드를 발생시킵니다.

간단한 예제를 통해 살펴보겠습니다:

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

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const fetchUser = async () => {
      try {
        setLoading(true);
        const response = await fetch(`https://api.example.com/users/${userId}`);
        const data = await response.json();
        setUser(data);
      } catch (error) {
        console.error('사용자 정보를 불러오는데 실패했습니다:', error);
      } finally {
        setLoading(false);
      }
    };

    fetchUser();
  }, [userId]); // userId가 변경될 때마다 실행

  if (loading) return <div>로딩 중...</div>;
  if (!user) return <div>사용자를 찾을 수 없습니다.</div>;

  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
}

useEffect의 의존성 배열

useEffect의 두 번째 인자로 전달되는 의존성 배열은 매우 중요합니다. 세 가지 사용 방식이 있습니다:

  1. 빈 배열 ([])

    useEffect(() => {
    console.log('컴포넌트가 마운트될 때만 실행됩니다');
    }, []);
  2. 의존성이 있는 배열 ([dep1, dep2])

    function Counter({ initialCount }) {
    const [count, setCount] = useState(initialCount);
    
    useEffect(() => {
     document.title = `현재 카운트: ${count}`;
    }, [count]); // count가 변경될 때마다 실행
    
    return (
     <button onClick={() => setCount(count + 1)}>
       증가
     </button>
    );
    }
  3. 의존성 배열 생략

    useEffect(() => {
    console.log('모든 렌더링마다 실행됩니다');
    });

클린업 함수 사용하기

타이머나, 구독형 디자인 패턴을 활용한 사이드 이펙트의 경우 코드가 종료될 때 클린업해주는 것이 일반적입니다.
useEffect에서는 return 값에 클린업 함수를 추가하여 쉽게 적용할 수 있습니다.

클린업 함수는 컴포넌트가 언마운트되거나 의존성이 변경되기 전에 실행됩니다. 구독 해제나 타이머 정리 등에 유용합니다:

function ChatRoom({ roomId }) {
  const [messages, setMessages] = useState([]);

  useEffect(() => {
    const connection = createConnection(roomId);
    connection.connect();

    const handleMessage = (message) => {
      setMessages(prev => [...prev, message]);
    };

    connection.on('message', handleMessage);

    // 클린업 함수
    return () => {
      connection.off('message', handleMessage);
      connection.disconnect();
    };
  }, [roomId]);

  return (
    <div>
      {messages.map(msg => (
        <div key={msg.id}>{msg.text}</div>
      ))}
    </div>
  );
}

useEffect 사용 시 주의사항

useEffect로 사이드 이펙트를 구현할 때는 다음과 같은 주의사항을 준수해야 합니다.

  1. 무한 루프 방지하기
    // ❌ 잘못된 사용
    useEffect(() => {
    setCount(count + 1);
    }, [count]); // count가 변경될 때마다 effect가 실행되어 무한 루프 발생
    

// ✅ 올바른 사용
useEffect(() => {
setCount(prev => prev + 1);
}, []); // 마운트 시에만 실행


2. **불필요한 의존성 피하기**
```javascript
// ❌ 불필요한 의존성
const handleScroll = () => {
  console.log(count);
};

useEffect(() => {
  window.addEventListener('scroll', handleScroll);
  return () => window.removeEventListener('scroll', handleScroll);
}, [handleScroll]); // 매 렌더링마다 새로운 함수가 생성됨

// ✅ useCallback 사용하기
const handleScroll = useCallback(() => {
  console.log(count);
}, [count]);

useEffect(() => {
  window.addEventListener('scroll', handleScroll);
  return () => window.removeEventListener('scroll', handleScroll);
}, [handleScroll]);

정리

useEffect는 React 컴포넌트에서 사이드 이펙트를 관리하는 강력한 도구입니다. 의존성 배열을 올바르게 관리하고, 클린업 함수를 적절히 사용한다면 메모리 누수를 방지하고 효과적인 애플리케이션 코드를 작성할 수 있습니다.

반응형