React에서 useEffect를 꼭 써야 할까? — 오히려 안 써도 되는 경우가 더 많다
React를 쓰다 보면 useEffect
를 거의 습관처럼 쓰게 되는 경우가 많다.
상태를 바꾸거나, 데이터를 갱신하거나, 뭔가 “변화”가 있으면 useEffect
부터 떠오른다.
하지만 React 공식 문서에서는 이렇게 말한다:
"You might not need an effect."
(공식 문서: https://react.dev/learn/you-might-not-need-an-effect)
📌 useEffect는 React의 '비상구'다
React는 기본적으로 선언형 프로그래밍 철학을 따르고 있다.
하지만 세상에는 선언형으로 다 표현할 수 없는 일들이 있다.
- 브라우저 타이틀 변경
- 외부 API 호출
- 이벤트 리스너 등록
- 애니메이션 트리거
- 타이머 설정 등
이런 것들을 처리하기 위해 등장한 게 바로 useEffect
다.
그래서 React 팀은 "useEffect는 React의 비상구(Escape Hatch)"라고 표현한다.
🚨 하지만 많은 useEffect는 '불필요'하다
React 공식 문서에서는 다음과 같은 상황에서 굳이 useEffect를 쓸 필요가 없다고 말한다.
1. 상태를 기반으로 어떤 값을 계산할 때
const [items, setItems] = useState([]);
const [visibleItems, setVisibleItems] = useState([]);
useEffect(() => {
setVisibleItems(items.filter(item => item.visible));
}, [items]);
이건 useEffect를 쓸 필요가 없다.
const visibleItems = items.filter(item => item.visible);
→ 이렇게 렌더링 중 계산하면 더 선언적이고 예측 가능하다.
2. 입력값을 상위 상태로 전달(lifting state)할 때
가끔 이런 코드를 볼 수 있다:
function ChildInput({ onValueChange }) {
const [localValue, setLocalValue] = useState('');
useEffect(() => {
onValueChange(localValue);
}, [localValue]);
return <input value={localValue} onChange={e => setLocalValue(e.target.value)} />;
}
위 코드는 중복된 상태(localValue)를 만들고, Effect로 부모에게 값을 전달한다.
하지만 이건 불필요하게 복잡하고 절차적이다.
더 나은 방식은 onChange 이벤트를 직접 부모에 전달하는 것이다:
function ChildInput({ value, onValueChange }) {
return <input value={value} onChange={e => onValueChange(e.target.value)} />;
}
→ 이렇게 하면 useEffect
없이도 깔끔하게 상태를 lifting 할 수 있다.
불필요한 useEffect는 선언형 구조를 흐트러뜨릴 수 있다.
✅ 언제 useEffect를 정말로 써야 할까?
공식 문서에서는 다음과 같은 상황에서만 useEffect 사용이 적절하다고 본다:
📡 서버로부터 데이터를 가져올 때
useEffect(() => {
fetch('/api/data')
.then(res => res.json())
.then(setData);
}, []);
🧭 브라우저 API와 동기화할 때 (scroll, resize 등)
useEffect(() => {
const handleResize = () => console.log(window.innerWidth);
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
⏱️ 타이머, setInterval 등 사용할 때
useEffect(() => {
const id = setInterval(() => setCount(c => c + 1), 1000);
return () => clearInterval(id);
}, []);
📦 외부 라이브러리와 상호작용할 때 (예: chart.js)
useEffect(() => {
const chart = new Chart(ctx, { type: 'bar', data });
return () => chart.destroy();
}, [data]);
💡 useEffect가 많아질수록 코드가 절차형으로 변한다
useEffect는 선언형 UI 흐름을 깨고, "이 시점에 이것을 수행하라"는 명령형 로직을 도입한다.
그래서 남용하게 되면, 다음과 같은 문제가 생긴다:
- 의존성 배열 관리가 복잡해짐
- 버그 추적이 어려워짐
- 불필요한 re-render나 무한 루프 발생
React 팀은 이런 문제를 줄이기 위해 useEffect를 최소화하고, 선언형 상태 관리 방식을 더 권장하고 있다.
🧭 마무리: "useEffect는 꼭 필요한 곳에만"
React는 선언형.
useEffect는 절차형.
그래서 필요할 때만 절제해서 써야 한다.
앞으로 코드를 짤 때 useEffect
부터 생각하지 말고, “정말 이게 side effect인가?”를 먼저 자문해보자.
대부분의 경우, 렌더링 중 계산이나 상태 흐름으로 충분히 처리할 수 있다.
🔗 참고 링크:
https://react.dev/learn/you-might-not-need-an-effect
'Web Development > Next.js' 카테고리의 다른 글
[RxJS] 2편. React에서 RxJS로 상태관리를 한다는 것은? (0) | 2025.03.23 |
---|---|
[RxJS] 1편. RxJS와 선언형 프로그래밍의 관계 (0) | 2025.03.23 |
React를 쓰면서도 몰랐던 선언형 프로그래밍 이야기 (0) | 2025.03.23 |
[Next.js] Next.js 라우팅 방식의 장점과 다른 방식들과의 차이점 (0) | 2024.12.22 |
[Next.js] 왜 우리는 Next.js를 사용하게 되었을까? (1) | 2024.12.22 |