| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | ||||||
| 2 | 3 | 4 | 5 | 6 | 7 | 8 |
| 9 | 10 | 11 | 12 | 13 | 14 | 15 |
| 16 | 17 | 18 | 19 | 20 | 21 | 22 |
| 23 | 24 | 25 | 26 | 27 | 28 | 29 |
| 30 | 31 |
- 인텔리제이
- 문자열
- 단위테스트
- vscode
- 스프링부트
- js
- HashMap
- html
- SpringBoot
- input
- 자바
- IntelliJ
- Visual Studio Code
- junit5
- java테스트
- ArrayList
- Eclipse
- 자바문법
- string
- CSS
- junit
- 정규식
- list
- math
- Java
- Array
- 자바스크립트
- 배열
- javascript
- 테스트자동화
- Today
- Total
어제 오늘 내일
[React] useEffect 사용법 (개념, 의존성 배열, Clean-up 함수) 본문
React 컴포넌트를 작성하다 보면 서버에서 데이터를 불러오거나(Data Fetching), 타이머를 설정하거나, 브라우저 DOM을 직접 조작하는 등의 작업이 필요합니다.
이처럼 컴포넌트의 순수한 렌더링 과정 외에 외부 시스템과 동기화하거나 발생하는 부수 효과를 React에서는 Side Effect(사이드 이펙트)라고 부릅니다. 그리고 이러한 Side Effect를 안전하게 처리하도록 도와주는 대표적인 Hook이 바로 useEffect입니다.
1. useEffect 기본 구조
useEffect는 [실행할 콜백 함수, 의존성 배열]이라는 두 개의 인자를 받습니다.
import React, { useEffect } from 'react';
useEffect(() => {
// 1. 실행할 Side Effect 로직 (컴포넌트 렌더링 후 실행됨)
return () => {
// 2. Clean-up 함수 (선택 사항)
// 컴포넌트가 언마운트되거나 Effect가 재실행되기 직전에 실행됨
};
}, [/* 3. 의존성 배열 (Dependency Array) */]);
React는 기본적으로 컴포넌트가 화면에 렌더링(또는 리렌더링)된 이후에 useEffect 안의 콜백 함수를 실행합니다.
2. 의존성 배열(Dependency Array)의 3가지 패턴
useEffect의 두 번째 인자인 의존성 배열(deps)을 어떻게 넘겨주느냐에 따라 Effect가 실행되는 시점이 완전히 달라집니다.
① 의존성 배열을 생략하는 경우: 매 렌더링마다 실행
의존성 배열을 아예 전달하지 않으면, 컴포넌트가 처음 나타날 때(Mount)와 리렌더링될 때마다 실행됩니다.
import React, { useState, useEffect } from 'react';
function AlwaysRun() {
const [count, setCount] = useState(0);
const [text, setText] = useState('');
// ❌ 두 번째 인자(배열)를 넘기지 않음
useEffect(() => {
// count가 바뀌든, text가 바뀌든 매 렌더링 후 항상 실행됩니다.
console.log('컴포넌트가 렌더링될 때마다 실행됩니다!');
});
return (
<div>
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
<input value={text} onChange={(e) => setText(e.target.value)} />
</div>
);
}
② 빈 배열([])을 전달하는 경우: 마운트 시 1회만 실행
빈 배열을 넘기면 컴포넌트가 화면에 처음 그려질 때(Mount) 단 한 번만 실행됩니다. API 호출이나 초기 설정 시 가장 많이 사용되는 형태입니다.
import React, { useState, useEffect } from 'react';
function FetchUserData() {
const [user, setUser] = useState(null);
// ✅ 빈 배열([])을 두 번째 인자로 전달
useEffect(() => {
// 컴포넌트가 처음 화면에 나타날 때 딱 1번만 실행됩니다.
console.log('데이터를 서버에서 가져옵니다.');
fetch('https://jsonplaceholder.typicode.com/users/1')
.then((res) => res.json())
.then((data) => setUser(data));
}, []); // 의존하는 값이 없으므로 최초 마운트 시 1회만 동작
return (
<div>
<h3>사용자 정보</h3>
{user ? <p>이름: {user.name}</p> : <p>로딩 중...</p>}
</div>
);
}
③ 특정 값을 배열에 넣는 경우: 의존하는 값이 변경될 때 실행
의존성 배열에 특정 변수를 넣으면, 초기 마운트 시 1회 실행 + 배열에 지정한 값이 변경되어 리렌더링될 때마다 실행됩니다.
import React, { useState, useEffect } from 'react';
function UserDetail({ userId }) {
const [userInfo, setUserInfo] = useState(null);
// ✅ userId 변경을 감지하도록 배열에 포함
useEffect(() => {
console.log(`User ID ${userId} 번 데이터 조회 중...`);
fetch(`https://jsonplaceholder.typicode.com/users/${userId}`)
.then((res) => res.json())
.then((data) => setUserInfo(data));
// userId props가 변경될 때마다 이 콜백 함수가 다시 실행됩니다.
}, [userId]);
return (
<div>
<h4>선택된 사용자: {userInfo?.name}</h4>
</div>
);
}
3. 정리 함수 (Clean-up Function)
useEffect 내부에서 return으로 함수를 반환하면, 이를 정리(Clean-up) 함수라고 부릅니다.
- 실행 시점: 컴포넌트가 화면에서 사라질 때(Unmount) 또는 Effect가 재실행되기 직전
- 역할: 타이머 해제(
clearInterval), 이벤트 리스너 제거(removeEventListener), WebSocket 연결 종료 등 메모리 누수(Memory Leak)를 방지하는 작업을 처리합니다.
import React, { useState, useEffect } from 'react';
function Timer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
// 1초마다 seconds 상태를 1씩 증가시키는 타이머 생성
const timerId = setInterval(() => {
setSeconds((prev) => prev + 1);
}, 1000);
console.log('타이머가 시작되었습니다.');
// ✅ Clean-up 함수 반환
return () => {
// 컴포넌트가 사라지거나(Unmount) Effect가 재실행될 때
// 이전에 등록된 타이머를 제거하여 메모리 누수를 막습니다.
clearInterval(timerId);
console.log('타이머가 정리(Clean-up)되었습니다.');
};
}, []); // 빈 배열: 최초 마운트 시 타이머 시작, 언마운트 시 클린업 실행
return <div>경과 시간: {seconds}초</div>;
}
export default Timer;
4. useEffect 사용 시 실무 필수 주의사항 3가지
① 무한 루프(Infinite Loop)를 주의하세요
useEffect 내부에서 state를 변경하고, 그 state를 다시 의존성 배열에 넣으면 무한 루프가 발생합니다.
// ❌ 무한 루프 발생 예시
const [count, setCount] = useState(0);
useEffect(() => {
// count 상태를 업데이트 -> 컴포넌트 리렌더링 -> useEffect 재실행 -> 또 setCount -> 무한 반복!
setCount(count + 1);
}, [count]);
② useEffect 내부에서 async/await 직접 사용 금지
useEffect의 첫 번째 인자인 콜백 함수는 반환값으로 오직 정리(Clean-up) 함수만 가질 수 있습니다.
그러나 async 함수는 기본적으로 Promise 객체를 반환하므로 문법적으로 올바르지 않습니다.
// ❌ 잘못된 사용법 (async 키워드를 직접 붙임)
useEffect(async () => {
const response = await fetch('/api/data');
}, []);
// ✅ 올바른 사용법 (내부에 async 함수를 정의하고 호출)
useEffect(() => {
// 내부에서 비동기 함수 선언
const fetchData = async () => {
try {
const response = await fetch('/api/data');
const data = await response.json();
console.log(data);
} catch (error) {
console.error('에러 발생:', error);
}
};
fetchData(); // 비동기 함수 실행
}, []);
③ 단순 데이터 계산은 useEffect 대신 일반 로직으로 처리하세요
State나 Props가 변경되었을 때 단순히 값을 재계산하거나 변환하는 작업은 useEffect를 사용하는 것보다 컴포넌트 바디(렌더링 과정)에서 바로 계산하는 것이 성능과 코드 가독성에 훨씬 좋습니다.
// ❌ 불필요한 useEffect 사용 (불필요한 추가 리렌더링 발생)
function BadFullName({ firstName, lastName }) {
const [fullName, setFullName] = useState('');
useEffect(() => {
// props가 바뀔 때마다 상태를 업데이트하므로 렌더링이 2번 일어남
setFullName(`${firstName} ${lastName}`);
}, [firstName, lastName]);
return <div>{fullName}</div>;
}
// ✅ 올바른 접근 (렌더링 도중 바로 계산)
function GoodFullName({ firstName, lastName }) {
// 별도의 State나 useEffect 없이 렌더링 타임에 파생값을 계산합니다.
const fullName = `${firstName} ${lastName}`;
return <div>{fullName}</div>;
}
5. 핵심 요약
| 의존성 배열 형태 | 실행 시점 | 주요 사용 목적 |
생략 (useEffect(fn)) |
최초 마운트 + 모든 리렌더링 시 | 로깅, 매번 실행되어야 하는 조작 |
빈 배열 (useEffect(fn, [])) |
최초 마운트 시 1회만 | 초기 API 데이터 로딩, 글로벌 이벤트 등록 |
값 포함 (useEffect(fn, [a, b])) |
최초 마운트 + 의존성(a, b) 변경 시 | 특정 props/state 변화에 따른 동기화 |
Clean-up 함수 (return () => {}) |
컴포넌트 언마운트 또는 Effect 재실행 직전 | 이벤트 제거, 타이머 정지, 구독 해제 |
'IT > React' 카테고리의 다른 글
| [React] Context API 완벽 가이드: Props Drilling 없이 상태 공유하기 (0) | 2026.08.03 |
|---|---|
| [React] props와 state 차이 이해하기, 상태 끌어올리기 (0) | 2026.08.02 |
| [React] useState 사용법 (개념, 흔히 하는 실수, 주의할 점) (0) | 2026.08.02 |
| [React] AboutController로 비동기 처리하기 (0) | 2026.08.02 |
| React StrictMode 완벽 가이드: 왜 내 코드는 두 번 실행될까? (0) | 2026.08.02 |