카테고리 없음

✨ useEffect 란? (What is useEffect?)

lamarcK 2025. 4. 7. 23:15

useEffect는 React 함수형 컴포넌트 내에서 **부수 효과(Side Effects)**를 수행하기 위해 사용하는 **훅(Hook)**이다. 부수 효과란 컴포넌트 렌더링 과정 외에 애플리케이션의 상태에 영향을 미치거나 외부 시스템과 상호작용하는 모든 작업을 의미한다. useEffect는 컴포넌트가 렌더링된 후에 이러한 부수 효과 함수를 실행하도록 예약한다.

🤔 왜 필요할까? (Why is it Needed?)

React의 주된 역할은 상태(State)와 속성(Props)을 기반으로 UI를 선언적으로(Declaratively) 렌더링하는 것이다. 하지만 애플리케이션은 단순히 UI만 렌더링하는 것 외에도 다양한 작업을 수행해야 한다. 예를 들면 다음과 같다.

  • 서버로부터 데이터 가져오기(Data Fetching)
  • 구독(Subscription) 설정 또는 해제 (예: 웹소켓, 이벤트 리스너)
  • 타이머(Timer) 설정 또는 해제 (setTimeout, setInterval)
  • 수동으로 DOM 조작(Manual DOM Manipulation)
  • **로컬 스토리지(Local Storage)**에 접근

이러한 작업들은 컴포넌트의 렌더링 결과와 직접적인 관련이 없을 수 있으며, 때로는 컴포넌트 외부의 상태에 영향을 준다. 이런 작업들을 "부수 효과"라고 부르며, useEffect는 이러한 부수 효과를 함수형 컴포넌트 내에서 격리하고 관리할 수 있는 공식적인 방법을 제공한다.

💾 사용법 (Syntax)

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

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

  useEffect(() => {
    // 이 함수는 컴포넌트가 렌더링된 후에 실행될 부수 효과 코드이다.
    console.log('Component rendered or dependency changed.');
    document.title = `You clicked ${count} times`;

    // 필요하다면 클린업(cleanup) 함수를 반환할 수 있다.
    return () => {
      console.log('Cleanup before next effect run or unmount.');
      // 예: 구독 해제, 타이머 제거 등
    };
  }, [count, someProp]); // 의존성 배열 (Dependency Array)

  return (
    <div>
      <p>Prop: {someProp}, Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}
  • useEffect는 두 개의 인자를 받는다.
    1. 부수 효과 함수 (Effect Function): 수행하고자 하는 부수 효과 코드를 담고 있는 함수.
    2. 의존성 배열 (Dependency Array): (선택 사항) 이 배열 안에 포함된 값들 중 하나라도 이전 렌더링과 비교했을 때 변경되면, 첫 번째 인자로 전달된 부수 효과 함수가 다시 실행된다.

⚙️ 작동 방식: 의존성 배열 (How it Works: Dependency Array)

useEffect의 핵심 동작 방식은 의존성 배열에 의해 결정된다.

  1. 의존성 배열 생략: useEffect(() => { ... });
    • 컴포넌트가 렌더링될 때마다 (최초 렌더링 포함) 부수 효과 함수가 실행된다.
    • 주의: 상태 업데이트 로직 등이 부수 효과 함수 내에 잘못 포함되면 **무한 루프(Infinite Loop)**를 유발할 수 있으므로 신중하게 사용해야 한다.
  2. 빈 의존성 배열 []: useEffect(() => { ... }, []);
    • 부수 효과 함수는 최초 렌더링 직후 한 번만 실행된다.
    • 이후 리렌더링 시에는 다시 실행되지 않는다.
    • 클래스 컴포넌트의 componentDidMount와 유사하게 동작한다.
    • 목적: 컴포넌트 마운트 시 필요한 초기 설정 작업 (최초 데이터 로딩, 이벤트 리스너 설정 등)에 주로 사용된다.
  3. 의존성 배열에 값 포함 [dep1, dep2]: useEffect(() => { ... }, [dep1, dep2]);
    • 부수 효과 함수는 최초 렌더링 직후 한 번 실행되고, 이후에는 의존성 배열([dep1, dep2]) 안의 값 중 하나라도 변경될 때마다 다시 실행된다.
    • React는 이전 렌더링과 현재 렌더링의 의존성 배열 값을 **얕은 비교(Shallow Comparison)**하여 변경 여부를 판단한다.
    • 클래스 컴포넌트의 componentDidUpdate와 유사한 동작을 구현할 수 있다 (물론 componentDidMount의 동작도 포함한다).
    • 목적: 특정 상태나 props의 변경에 반응하여 특정 작업을 수행해야 할 때 사용된다. (예: 사용자 ID가 변경되면 해당 사용자의 데이터 다시 불러오기)

🧹 클린업 함수 (Cleanup Function)

useEffect의 부수 효과 함수는 선택적으로 클린업(Cleanup) 함수를 반환할 수 있다.

JavaScript
 
useEffect(() => {
  // 부수 효과 설정 (예: 구독)
  const subscription = someApi.subscribe();
  console.log('Subscribed!');

  // 클린업 함수 반환
  return () => {
    // 부수 효과 해제 (예: 구독 해제)
    subscription.unsubscribe();
    console.log('Unsubscribed!');
  };
}, []); // 의존성 배열
  • 언제 실행되나?
    1. 컴포넌트가 **언마운트(Unmount)**될 때 (사라질 때).
    2. 의존성 배열이 변경되어 부수 효과 함수가 다시 실행되기 직전에 이전 효과를 정리하기 위해 실행된다. (빈 배열 []인 경우 언마운트 시에만 실행)
  • 목적: 메모리 누수(Memory Leaks)나 불필요한 동작을 방지하기 위해 설정했던 부수 효과를 정리하는 역할을 한다. (예: 타이머 제거, 이벤트 리스너 해제, 웹소켓 연결 종료, 구독 해제 등)

✅ 주요 사용 사례 (Key Use Cases)

  • API 데이터 가져오기: 컴포넌트 마운트 시 또는 특정 조건 변경 시 서버 데이터 요청.
  • 이벤트 리스너 설정/해제: window나 document 등에 이벤트 리스너 추가 및 제거.
  • 타이머/인터벌 설정/해제: setTimeout, setInterval 사용 및 clearTimeout, clearInterval 호출.
  • 외부 라이브러리 연동: React와 직접 관련 없는 외부 라이브러리 초기화 및 해제.
  • 수동 DOM 업데이트: (가급적 useRef 등을 활용하는 것이 좋지만) 필요한 경우 DOM 직접 조작.

⚠️ 주의사항 및 모범 사례 (Considerations & Best Practices)

  • 의존성 배열의 정확성: 부수 효과 함수 내에서 사용되는 모든 컴포넌트 범위의 값(props, state, 함수 등)은 의존성 배열에 포함시켜야 한다. 누락 시 클로저(Closure) 문제로 인해 예전 값(Stale Value)을 참조하여 예상치 못한 버그가 발생할 수 있다. ESLint의 react-hooks/exhaustive-deps 규칙을 활성화하면 이를 도와준다.
  • 무한 루프 방지: useEffect 내에서 상태를 업데이트할 때는 해당 상태가 의존성 배열에 포함되지 않도록 주의하거나, 상태 업데이트가 특정 조건에서만 발생하도록 로직을 구성해야 한다.
  • 함수 의존성: 컴포넌트 내부에 정의된 함수를 useEffect의 의존성 배열에 포함해야 할 경우, 해당 함수가 리렌더링 시마다 재생성되어 불필요하게 효과가 재실행될 수 있다. 이때는 함수를 useEffect 내부로 옮기거나, useCallback 훅으로 함수 자체를 메모이제이션하여 의존성 배열에 포함시키는 것이 좋다. Highlight: useCallback
  • 비동기(Async) 함수: useEffect의 첫 번째 인자인 부수 효과 함수 자체는 async 함수가 될 수 없다 (클린업 함수를 반환해야 하기 때문). 비동기 작업이 필요하면 함수 내부에 별도의 async 함수를 정의하고 호출하거나 즉시 실행 함수(IIFE) 형태로 사용해야 한다.
    JavaScript
     
    useEffect(() => {
      const fetchData = async () => {
        // 비동기 로직
      };
      fetchData();
    }, [dependency]);
    

💡 비교: 클래스 컴포넌트 생명주기 (Comparison: Class Component Lifecycles)

useEffect는 클래스 컴포넌트의 여러 생명주기 메서드의 목적을 하나로 통합한다.

  • componentDidMount: useEffect(() => {...}, []) (빈 의존성 배열)
  • componentDidUpdate: useEffect(() => {...}, [dep1, dep2]) (의존성 배열 포함)
  • componentWillUnmount: useEffect에서 반환하는 클린업 함수

useEffect는 "렌더링 이후에 어떤 작업을 수행한다"는 관점에서 생각하는 것이 더 적합하며, 생명주기보다는 데이터 흐름과 동기화의 관점에서 접근하는 것이 좋다.