카테고리 없음
✨ 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는 두 개의 인자를 받는다.
- 부수 효과 함수 (Effect Function): 수행하고자 하는 부수 효과 코드를 담고 있는 함수.
- 의존성 배열 (Dependency Array): (선택 사항) 이 배열 안에 포함된 값들 중 하나라도 이전 렌더링과 비교했을 때 변경되면, 첫 번째 인자로 전달된 부수 효과 함수가 다시 실행된다.
⚙️ 작동 방식: 의존성 배열 (How it Works: Dependency Array)
useEffect의 핵심 동작 방식은 의존성 배열에 의해 결정된다.
- 의존성 배열 생략: useEffect(() => { ... });
- 컴포넌트가 렌더링될 때마다 (최초 렌더링 포함) 부수 효과 함수가 실행된다.
- 주의: 상태 업데이트 로직 등이 부수 효과 함수 내에 잘못 포함되면 **무한 루프(Infinite Loop)**를 유발할 수 있으므로 신중하게 사용해야 한다.
- 빈 의존성 배열 []: useEffect(() => { ... }, []);
- 부수 효과 함수는 최초 렌더링 직후 한 번만 실행된다.
- 이후 리렌더링 시에는 다시 실행되지 않는다.
- 클래스 컴포넌트의 componentDidMount와 유사하게 동작한다.
- 목적: 컴포넌트 마운트 시 필요한 초기 설정 작업 (최초 데이터 로딩, 이벤트 리스너 설정 등)에 주로 사용된다.
- 의존성 배열에 값 포함 [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!');
};
}, []); // 의존성 배열
- 언제 실행되나?
- 컴포넌트가 **언마운트(Unmount)**될 때 (사라질 때).
- 의존성 배열이 변경되어 부수 효과 함수가 다시 실행되기 직전에 이전 효과를 정리하기 위해 실행된다. (빈 배열 []인 경우 언마운트 시에만 실행)
- 목적: 메모리 누수(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는 "렌더링 이후에 어떤 작업을 수행한다"는 관점에서 생각하는 것이 더 적합하며, 생명주기보다는 데이터 흐름과 동기화의 관점에서 접근하는 것이 좋다.