본문 바로가기
Javascript/React.js

초보자를 위한 React useEffect 완벽 가이드

by daddydontsleep 2024. 11. 14.
728x90
728x90

초보자를 위한 React useEffect 완벽 가이드

안녕하세요! 오늘은 React의 가장 중요한 Hook 중 하나인 useEffect에 대해 자세히 알아보도록 하겠습니다. 처음 React를 배우시는 분들이 가장 어려워하는 부분 중 하나가 바로 이 useEffect인데요, 천천히 하나씩 살펴보면서 개념을 잡아보도록 하겠습니다.

목차

  1. useEffect란?
  2. useEffect가 필요한 이유
  3. 기본 사용법
  4. 의존성 배열 이해하기
  5. cleanup 함수 사용하기
  6. 자주 하는 실수와 해결방법
  7. 실전 예제

useEffect란?

useEffect는 React 컴포넌트에서 '부수 효과(Side Effect)'를 처리하기 위한 Hook입니다. 여기서 부수 효과란 무엇일까요?

  • API 호출
  • 이벤트 리스너 등록/해제
  • DOM 직접 조작
  • 타이머 설정/해제
  • 로컬 스토리지 접근

등이 모두 부수 효과에 해당합니다. 즉, 컴포넌트의 주요 역할인 '화면 렌더링' 외의 모든 작업들을 말합니다.

useEffect가 필요한 이유

React 컴포넌트의 주요 역할은 UI를 렌더링하는 것입니다. 하지만 실제 애플리케이션을 만들다 보면 UI 렌더링 외에도 다양한 작업들이 필요합니다.

예를 들어볼까요?

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

  // 이렇게 하면 안됩니다!
  fetch(`/api/users/${userId}`)
    .then(response => response.json())
    .then(data => setUser(data));

  return <div>{user ? user.name : 'Loading...'}</div>
}

위 코드에서 fetch 호출을 직접 컴포넌트 내부에서 하면 어떤 문제가 발생할까요?

  1. 컴포넌트가 렌더링될 때마다 API를 호출하게 됩니다.
  2. 무한 루프에 빠질 수 있습니다. (데이터를 받아와서 setState -> 리렌더링 -> 다시 API 호출 -> ...)

이러한 문제를 해결하기 위해 useEffect가 필요한 것입니다!

기본 사용법

useEffect의 기본 형태는 다음과 같습니다:

useEffect(() => {
  // 실행하고 싶은 부수 효과 코드
}, [의존성 배열]);

간단한 예제를 통해 살펴볼까요?

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

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

  useEffect(() => {
    document.title = `현재 카운트: ${count}`;
  }, [count]);

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

이 예제에서는 count 값이 변경될 때마다 문서의 제목을 업데이트합니다.

의존성 배열 이해하기

의존성 배열은 useEffect의 실행 시점을 제어하는 중요한 매개변수입니다. 크게 세 가지 경우가 있습니다:

1. 빈 배열 ([])

useEffect(() => {
  console.log('컴포넌트가 마운트될 때만 실행됩니다');
}, []);
  • 컴포넌트가 처음 마운트될 때만 실행
  • 주로 초기 데이터 로딩, 이벤트 리스너 등록에 사용

2. 의존성 있는 배열 ([dep1, dep2, ...])

useEffect(() => {
  console.log('count나 name이 변경될 때마다 실행됩니다');
}, [count, name]);
  • 지정된 값들이 변경될 때마다 실행
  • 여러 개의 의존성을 지정할 수 있음

3. 의존성 배열 생략

useEffect(() => {
  console.log('모든 렌더링 후에 실행됩니다');
});
  • 컴포넌트가 리렌더링될 때마다 실행
  • 되도록 사용을 피하는 것이 좋음

cleanup 함수 사용하기

useEffect에서 반환하는 함수를 cleanup 함수라고 합니다. 이 함수는 다음과 같은 경우에 실행됩니다:

  • 컴포넌트가 언마운트될 때
  • 다음 effect가 실행되기 전에
useEffect(() => {
  // 이벤트 리스너 등록
  const handleScroll = () => {
    console.log(window.scrollY);
  };
  window.addEventListener('scroll', handleScroll);

  // cleanup 함수
  return () => {
    window.removeEventListener('scroll', handleScroll);
  };
}, []); // 빈 배열이므로 마운트/언마운트시에만 실행

cleanup 함수는 특히 다음과 같은 경우에 중요합니다:

  • 이벤트 리스너 해제
  • WebSocket 연결 종료
  • 타이머 정리
  • API 요청 취소

자주 하는 실수와 해결방법

1. 의존성 배열 누락

// ❌ 잘못된 예시
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    fetchUser(userId);
  }, []); // userId가 의존성 배열에 없음!

  // ...
}

// ✅ 올바른 예시
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    fetchUser(userId);
  }, [userId]);

  // ...
}

2. 불필요한 의존성 추가

// ❌ 잘못된 예시
function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const id = setInterval(() => {
      setCount(count + 1);
    }, 1000);
    return () => clearInterval(id);
  }, [count]); // count를 의존성으로 추가하면 인터벌이 매번 재설정됨

  // ✅ 올바른 예시
  useEffect(() => {
    const id = setInterval(() => {
      setCount(prev => prev + 1);
    }, 1000);
    return () => clearInterval(id);
  }, []); // 함수형 업데이트를 사용하면 의존성이 필요 없음
}

실전 예제

실제 프로젝트에서 자주 사용되는 useEffect 패턴들을 살펴보겠습니다.

1. API 데이터 로딩

function UserList() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchUsers = async () => {
      try {
        const response = await fetch('https://api.example.com/users');
        const data = await response.json();
        setUsers(data);
        setLoading(false);
      } catch (err) {
        setError(err.message);
        setLoading(false);
      }
    };

    fetchUsers();
  }, []); // 컴포넌트 마운트 시 한 번만 실행

  if (loading) return <div>로딩 중...</div>;
  if (error) return <div>에러: {error}</div>;

  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

2. 폼 데이터 자동 저장

function AutoSaveForm() {
  const [text, setText] = useState('');

  useEffect(() => {
    const timeoutId = setTimeout(() => {
      localStorage.setItem('draftText', text);
      console.log('자동 저장됨');
    }, 1000);

    return () => clearTimeout(timeoutId);
  }, [text]);

  return (
    <textarea
      value={text}
      onChange={e => setText(e.target.value)}
      placeholder="입력하면 자동으로 저장됩니다..."
    />
  );
}

3. 윈도우 크기 감지

function WindowSizeTracker() {
  const [windowSize, setWindowSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight
  });

  useEffect(() => {
    const handleResize = () => {
      setWindowSize({
        width: window.innerWidth,
        height: window.innerHeight
      });
    };

    window.addEventListener('resize', handleResize);

    return () => window.removeEventListener('resize', handleResize);
  }, []);

  return (
    <div>
      <p>현재 윈도우 크기:</p>
      <p>너비: {windowSize.width}px</p>
      <p>높이: {windowSize.height}px</p>
    </div>
  );
}

마무리

useEffect는 처음에는 어렵게 느껴질 수 있지만, 기본 개념을 이해하고 나면 React 애플리케이션에서 정말 유용하게 사용할 수 있는 Hook입니다. 특히 다음 사항들을 기억하세요:

  1. useEffect는 '부수 효과'를 처리하기 위한 것입니다.
  2. 의존성 배열을 통해 실행 시점을 제어할 수 있습니다.
  3. cleanup 함수를 통해 리소스를 정리할 수 있습니다.
  4. 불필요한 실행을 피하기 위해 의존성 배열을 신중하게 설정해야 합니다.

이제 useEffect를 자신있게 사용하실 수 있을 거예요! 실제 프로젝트에서 다양한 방식으로 활용해보면서 더 깊이 있는 이해를 하시기 바랍니다. 😊

참고 자료

728x90
300x250