React의 useEffect 톺아보기! 🔍
useEffect는 React에서 함수형 컴포넌트의 라이프사이클과비동기 작업을 관리하는 데 사용되는 강력한 Hook입니다. 이 글에서는 useEffect의 기본 개념부터 고급 사용법, 동작 원리, 그리고 실무에서 마주할 수 있는 문제 해결 방법까지 다뤄보겠습니다.
useEffect란 무엇인가요? 🤔
useEffect는 React의 함수형 컴포넌트에서 컴포넌트가 렌더링된 이후 실행되는 작업을 정의할 수 있는 Hook이자, 부수 효과(side effects)를 처리하기 위해 사용됩니다.
부수 효과란?
- 서버에서 데이터를 가져오기
- 브라우저 타이틀 업데이트
- 이벤트 리스너 추가 및 정리
- 타이머 설정 및 정리
클래스형 컴포넌트에서는 componentDidMount, componentDidUpdate, componentWillUnmount로 나뉘어 처리하던 작업을 함수형 컴포넌트에서는 useEffect 하나로 통합해서 처리할 수 있습니다.
useEffect의 기본 문법 📚
useEffect(() => {
// 실행할 코드
return () => {
// Cleanup (선택 사항)
};
}, [의존성 배열]);
매개변수
- Effect 함수: 실행할 작업(콜백 함수)
- 의존성 배열: useEffect가 실행되는 조건을 지정. 생략 시 매번 렌더링될 때 실행됩니다.
useEffect의 기본 사용법 🛠️
기본 예제: 컴포넌트가 렌더링될 때 실행하기
import React, { useState, useEffect } from "react";
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log(`You clicked ${count} times`);
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
실행 순서
- 컴포넌트가 렌더링된 후 useEffect 실행
- 상태가 변경되면 다시 렌더링 → useEffect 다시 실행.
useEffect의 동작 원리 ⚙️
- 렌더링 이후 실행
- 컴포넌트가 렌더링되고 DOM이 업데이트된 후에 실행
- 의존성 배열을 기반으로 조건부 실행
- 배열 내의 값이 변경될 때만 실행
- Cleanup 함수 실행
- 컴포넌트가 제거되거나, 다음 Effect 실행 전에 정리(clean-up) 작업을 실행
useEffect의 의존성 배열: 정말 중요한 개념! 🧠
의존성 배열은 useEffect가 언제 실행될지 결정하는 역할을 합니다.useEffect가 실행되는 시점을 제어할 수 있어요.
1. 의존성 배열이 없는 경우
- 컴포넌트가 렌더링될 때마다 실행됩니다.
useEffect(() => {
console.log("컴포넌트가 렌더링될 때마다 실행");
});
2. 의존성 배열이 있는 경우
- count가 변경될 때만 실행됩니다.
useEffect(() => {
console.log("count가 변경될 때만 실행됩니다!");
}, [count]);
3. 빈 배열([])
- 마운트 시 한 번만 실행됩니다.
useEffect(() => {
console.log("처음 렌더링 시에만 실행");
}, []);
4. 특정 상태나 props에 의존
- 배열에 명시된 값이 변경될 때만 실행됩니다.
useEffect(() => {
console.log("count가 변경될 때 실행");
}, [count]);
5. 복잡한 의존성 관리
- 여러 상태나 props를 의존성으로 설정 가능.
useEffect(() => {
console.log("count나 userName이 변경될 때 실행");
}, [count, userName]);
useEffect와 Cleanup 🧹
useEffect는 정리(cleanup) 함수를 반환할 수 있어요. Effect로 인해 발생한 부작용을 정리하는 데 사용됩니다. 이를 통해 타이머나 이벤트 리스너 같은 리소스를 정리할 수 있습니다.
언제 실행되나요?
- 컴포넌트가 제거될 때.
- 다음 Effect가 실행되기 전에.
Cleanup 예제: 타이머 정리
import React, { useState, useEffect } from "react";
function Timer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setSeconds((prev) => prev + 1);
}, 1000);
return () => {
clearInterval(interval); // 타이머 정리
console.log("Timer cleaned up!");
};
}, []);
return <p>Elapsed time: {seconds} seconds</p>;
}
Cleanup이 필요한 이유
- 메모리 누수 방지
- 불필요한 리소스 소모 방지
useEffect의 실무 활용 🐾
1. API 호출 및 데이터 가져오기(Fetching Data)
서버에서 데이터를 가져오는 작업은 useEffect의 대표적인 활용 사례로 가장 자주 사용됩니다.
import React, { useState, useEffect } from "react";
function FetchData() {
const [data, setData] = useState([]);
useEffect(() => {
async function fetchData() {
const response = await fetch("https://jsonplaceholder.typicode.com/posts");
const result = await response.json();
setData(result);
}
fetchData();
}, []); // 빈 배열로 첫 렌더링 시에만 실행
return (
<ul>
{data.map((item) => (
<li key={item.id}>{item.title}</li>
))}
</ul>
);
}
2. 브라우저 타이틀 업데이트
import React, { useState, useEffect } from "react";
function TitleUpdater() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // count가 변경될 때만 실행
return <button onClick={() => setCount(count + 1)}>Click me</button>;
}
3. 이벤트 리스너 관리
import React, { useState, useEffect } from "react";
function MouseTracker() {
const [position, setPosition] = useState({ x: 0, y: 0 });
useEffect(() => {
const handleMouseMove = (event) => {
setPosition({ x: event.clientX, y: event.clientY });
};
window.addEventListener("mousemove", handleMouseMove);
return () => {
window.removeEventListener("mousemove", handleMouseMove); // 정리
};
}, []);
return (
<p>
Mouse position: {position.x}, {position.y}
</p>
);
}
4. 로컬 스토리지와 상태 동기화
import React, { useState, useEffect } from "react";
function LocalStorageSync() {
const [name, setName] = useState(() => localStorage.getItem("name") || "");
useEffect(() => {
localStorage.setItem("name", name);
}, [name]); // name이 변경될 때만 실행
return (
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Enter your name"
/>
);
}
useEffect에서 주의할 점 ⚠️
1. 의존성 배열 관리
- 의존성 배열을 생략하면 매 렌더링마다 실행됩니다.
- 의존성 배열이 불완전하면 예상치 못한 동작이 발생할 수 있습니다.
- ESLint 플러그인은 누락된 의존성을 경고해줍니다.
- 의존성 배열을 잘못 설정하면 무한 루프가 발생할 수 있어요.
useEffect(() => {
setState(state + 1); // 무한 루프
}, [state]); // 매번 상태가 변경되어 다시 실행
2. 동기화 문제
- useEffect는 렌더링 이후에 실행되므로, 초기 렌더링 중 데이터를 사용할 경우 렌더링이 두 번 발생할 수 있습니다.
- useEffect 내부에서 비동기 함수를 직접 사용하면 예기치 못한 동작이 발생할 수 있습니다. 대신, 내부에 async 함수를 선언하세요.
useEffect(() => {
async function fetchData() {
const data = await someAPI();
console.log(data);
}
fetchData();
}, []);
3. 의존성 배열의 객체 비교
- 객체나 배열은 참조 값이 변경될 수 있으므로, 이를 의존성으로 사용할 때는 주의가 필요합니다.
- useEffect가 실행될 때마다 Cleanup이 실행되므로, 불필요한 정리가 발생하지 않도록 신중해야 합니다.
useEffect로 구현한 종합 예제 🎯
할 일 관리 앱에서의 useEffect
- API를 통해 초기 데이터를 가져오고, 상태를 저장하며, 컴포넌트가 제거될 때 정리 작업을 수행.
import React, { useState, useEffect } from "react";
function TodoApp() {
const [todos, setTodos] = useState([]);
const [task, setTask] = useState("");
useEffect(() => {
// 초기 데이터 가져오기
async function fetchTodos() {
const response = await fetch("https://jsonplaceholder.typicode.com/todos");
const data = await response.json();
setTodos(data.slice(0, 10)); // 10개만 가져오기
}
fetchTodos();
// Cleanup (여기선 예제 용도로만 작성)
return () => {
console.log("컴포넌트가 제거되었습니다.");
};
}, []);
const addTask = () => {
setTodos((prev) => [...prev, { id: Date.now(), title: task, completed: false }]);
setTask("");
};
return (
<div>
<h1>Todo List</h1>
<input value={task} onChange={(e) => setTask(e.target.value)} placeholder="Add a task" />
<button onClick={addTask}>Add</button>
<ul>
{todos.map((todo) => (
<li key={todo.id}>
<span>{todo.title}</span>
</li>
))}
</ul>
</div>
);
}
다중 상태 관리 및 API 호출
import React, { useState, useEffect } from "react";
function WeatherApp() {
const [location, setLocation] = useState("Seoul");
const [weather, setWeather] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
async function fetchWeather() {
try {
const response = await fetch(
`https://api.weatherapi.com/v1/current.json?key=YOUR_API_KEY&q=${location}`
);
if (!response.ok) throw new Error("Failed to fetch weather data");
const data = await response.json();
setWeather(data);
} catch (err) {
setError(err.message);
}
}
fetchWeather();
}, [location]);
return (
<div>
<h1>Weather App</h1>
<input
value={location}
onChange={(e) => setLocation(e.target.value)}
placeholder="Enter location"
/>
{error && <p>Error: {error}</p>}
{weather ? (
<div>
<h2>{weather.location.name}</h2>
<p>{weather.current.temp_c}°C</p>
<p>{weather.current.condition.text}</p>
</div>
) : (
<p>Loading...</p>
)}
</div>
);
}
결론 🏁
useEffect는 단순히 컴포넌트의 라이프사이클을 관리하는 것을 넘어, 비동기 작업, 이벤트 리스너, 상태 동기화 등을 모두 처리할 수 있는 강력한 도구입니다. 잘 이해하고 적절히 사용하면, React 애플리케이션의 성능과 유지보수성을 한 단계 끌어올릴 수 있습니다.
🌷전설의 개발자가 되어봅시당! 🌷
'Front-end > React' 카테고리의 다른 글
[React] useCallback: 함수 메모이제이션의 정석! 🛠️ (0) | 2024.12.11 |
---|---|
[React] useReducer: 상태 관리의 새로운 차원! 🎛️ (0) | 2024.12.10 |
[React] useMemo: 성능 최적화를 위한 비장의 무기 🛡️ (1) | 2024.12.08 |
[React] Hooks: 초보부터 전문가까지 완벽 정복 🪝 (1) | 2024.12.07 |
[React] render() 함수 완벽 가이드 ✨ (0) | 2024.12.06 |