Post

useEffect 실수 바로잡기

useEffect 실수 바로잡기

찾아보거나 알게된 배경

React를 쓰다 보면 자연스럽게 useEffect를 남발하게 된다.
실제로 React를 처음 배울 때 상태 관리와 흐름에 익숙하지 않게 되고, 뭔가 상태가 내 마음대로 바뀌지 않을 때 useEffect를 사용하게 된다. 그리고 그렇게 짜게되면, 대개 잘 돌아가는 것처럼 보이기 일쑤이다.

사실 AI 시대에 이걸 이해하지 않고 사용해도 알아서 잘 짜주겠지만…
useEffect는 특히나 잘못 사용하기 쉬운 hook인 것 같다.
맹목적으로 사용하다간ㄴ 어느 순간부터는 그냥 “뭔가 바뀌면 실행해야 할 것 같아서” 넣게 된다.

이번에는 React 공식 문서인 You Might Not Need an Effect 를 기반으로
정말로 언제 필요한지, 그리고 왜 대부분의 경우 필요 없는지 정리해보려고 한다.


요약

  • useEffect는 렌더링과 “외부 세계”를 동기화하기 위한 도구
  • 내부 상태 로직을 처리하기 위해 쓰는 순간 대부분 잘못된 사용
  • 많은 경우는
    → 계산으로 해결 가능하다
    → 이벤트 핸들러로 해결 가능하다
    → state 구조를 바꾸면 해결 가능하다

하나하나 알아보자.


useEffect의 본질

React에서 렌더링은 순수 함수처럼 동작해야 한다.

즉, 같은 props나 state에 의해서 → 같은 UI가 그려져야 한다!

그런데 현실에서는…

  • 서버에서 데이터 가져오기
  • DOM 직접 조작
  • 외부 라이브러리 연결
    등을 해야할 때가 있는데, 이건 React 내부에서 해결할 수가 없다. DOM을 직접 조작하는걸 리액트 내부에서 처리하기가 힘들다는 것이다.

그래서 useEffect가 “React 외부와 동기화”를 위해 나왔고, 이 역할이 아닌 useEffect의 사용은 (대부분) 안티패턴으로 봐도 무방하다.


우리가 실제로 하는 잘못된 사용

우리는 종종 이런 코드를 쓴다:

1
2
3
4
5
const [fullName, setFullName] = useState('');    
    
useEffect(() => {    
  setFullName(firstName + ' ' + lastName);    
}, [firstName, lastName]);  

겉보기에는 자연스럽지만 구조적으로 보면:

state → effect → state 업데이트 → re-render

즉, 불필요한 렌더링을 한 번 더 만든다.


계산으로 해결 가능한 경우

위 코드는 그냥 이렇게 쓰면 된다:

1
const fullName = firstName + ' ' + lastName;  

“동기화 착각” 문제

많은 사람들이 이렇게 생각한다:

“A가 바뀌면 B도 같이 바뀌어야 하니까 useEffect 써야지”

하지만 이건 대부분 derived state로 해결이 가능하다.
예를 들어,

const [filtered, setFiltered] = useState([]);

useEffect(() => {
setFiltered(items.filter(…));
}, [items]);

위와 같은 코드는 언뜻 item이 바뀌면 필터된 상태를 바꿔야한다는 우리의 직관과 맞아보인다. 하지만 아래와 같이 쓰는 게 리액트의 의도에 더 맞다.

const filtered = items.filter(…);

상태를 하나 더 두는 순간 복잡도와 버그 가능성이 증가한다.


이벤트 vs Effect

또 다른 흔한 실수는 다음과 같다.

useEffect(() => {
if (submitted) {
sendData();
}
}, [submitted]);

언뜻 ‘제출하고자 하는 값’이 변경되면 sendData를 하라는, 간단한 함수고 틀린게 없어보인ㄷ. 하지만 이건 이벤트에서 처리해야 한다:

function handleSubmit() {
sendData();
}

차이는 effect는 이미 일어난 변화에 대해 사이드 이펙트를 처리하는 것이지만 event는 사용자 행동에 반응한다는 점이다. 값을 제출하는건 결과적인게 아니라 유저의 행동이므로, event로 처리하는 게 더 맞다.


useEffect가 진짜 필요한 순간

여기까지 읽었다면 ‘그럼 왜 만들었고 언제쓰냐’라는 질문이 자연스레 따라오게 된다.

다음 경우에는 useEffect가 필요하다.

1. 외부 시스템과 연결

useEffect(() => {
const connection = connect();
return () => connection.disconnect();
}, []);

2. 데이터 fetching

useEffect(() => {
fetch(‘/api/data’).then(setData);
}, []);

3. DOM 직접 조작

useEffect(() => {
document.title = title;
}, [title]);


핵심 정리

useEffect를 쓰기 전에 이 질문 하나면 충분하다!

“이건 React 외부와 관련 있는가?”

  • 그렇다 → useEffect
  • 아니다 → 다른 방법

사실 요즘 데이터 페칭이나 외부 라이브러리도 자체적인 hook을 제공하지, 사용자가 useEffect를 쓰는 일은 더더욱 없다.

useEffect를 직접 작성할 일은 정말 없다. 오히려 useEffect를 잘 활용한 커스텀 훅을 써서 useEffect 자체가 밖으로 잘 드러나지 않게 하는 게 좋을 것 같다.

This post is licensed under CC BY 4.0 by the author.