useEffect, 함부로 쓰지 말자

유정정·2025년 5월 25일

생각해보기

목록 보기
4/5

1. useEffect, 너 없인 안 되는 줄 알았어

나는 리액트를 잘 몰랐을 때 상태 업데이트 할 때나, 계산할 때나, 컴포넌트 내부에서 무슨 일이 생기면 전부 다 useEffect에서 처리해야 하는 줄 알고 사용했었다. 그러다 공식문서를 읽고 여러 경험을 거치면서 useEffect를 그렇게 막 사용하면 안된다는 사실을 깨달았다. 아직 완벽히 이해했다고는 할 수 없지만, 이 글에서는 내가 공부한 내용을 바탕으로 useEffect의 올바른 사용법을 공유해보려고 한다.

2. 먼저 useEffect는 왜 존재할까?

리액트에서는 컴포넌트를 만들고 → 업데이트하고 → 없애는 일련의 과정을 생명주기(Life Cycle) 라고 부른다.
클래스 컴포넌트에서는 이 생명주기를 다루기 위해 아래와 같은 메서드를 제공한다.

class MyComponent extends React.Component {
  componentDidMount() { ... }       // 마운트 완료 후 1회
  componentDidUpdate() { ... }      // 상태나 props 변경 후
  componentWillUnmount() { ... }    // 컴포넌트가 사라지기 직전
}

그하지만 함수형 컴포넌트에는 이런 생명주기 메서드가 없다.
대신에 이 역할을 모두 useEffect 하나로 통합해서 처리할 수 있다.

useEffect(() => {
  // componentDidMount 또는 componentDidUpdate
  return () => {
    // componentWillUnmount
  }
}, []);

이게 정확히 무슨 말인지 몰라도 일단 useEffect는 컴포넌트가 생성되고, 변경되고, 사라지는 과정에서 필요한 작업을 실행할 수 있게 해주는 리액트의 공식 도구라고 생각하면 된다.

3. useEffect는 언제 쓰는거지?

리액트 공식문서에서는 useEffect를 아래와 같이 설명한다.

React의 state을 기준으로 React와 상관없는 구성 요소를 제어하거나, 서버 연결을 설정하거나, 구성 요소가 화면에 나타날 때 분석 목적의 로그를 전송할 수도 있습니다. Effect를 사용하면 렌더링 후 특정 코드를 실행하여 React 외부의 시스템과 컴포넌트를 동기화할 수 있습니다.

나는 이 설명을 보고 useEffect는 컴포넌트가 처음 렌더링되었거나, 상태나 props가 바뀌었을 때 그 변화에 따라 React 외부의 무언가를 조작하거나 동기화해야 할 때 사용하는 도구라고 이해했다.

중요한 건 useEffect렌더링이 끝난 뒤에 실행된다는 점이다.
이 구조 덕분에 화면이 그려지는 도중에 외부 작업(API 호출, DOM 접근 등)이 들어와서
렌더링 흐름이 꼬이거나 무한 루프에 빠지는 것을 방지할 수 있다.

그래서 useEffect는 React 외부와 상호작용이 필요한 상황에서만 사용하는 것이 좋다. 그 이유는 진짜 간단하게 생각하면 외부적인 작업은 React의 렌더링 흐름과 직접적인 관련이 없기 때문이다.

예를 들면

  • 서버로 데이터를 요청하거나
  • localStorage, sessionStorage 같은 브라우저 저장소를 접근하거나
  • 외부 라이브러리를 초기화하거나
  • window 객체에 이벤트를 등록하거나

이런 작업들은 모두 React가 제어할 수 없는 외부와의 연결이기 때문에, 렌더링이 끝난 후에 별도로 처리하는 것이 필요하다.

그래서 useEffect가 필요한 것이다.

4. useEffect 쓰지 않아야 하는 경우는?

반대로 React 내부 상태나 렌더링 결과만으로 처리할 수 있는 작업이라면 굳이 useEffect를 쓸 필요는 없다고 생각한다. 이런 경우에는 렌더링 중에 바로 계산해버리거나, 상황에 따라 useMemo, useCallback 같은 훅으로 처리하는 것이 훨씬 깔끔하고 직관적이다.

useEffect는 렌더링이 끝난 후에 실행된다는 점이 핵심이다. 그래서 렌더링 중에 이미 알 수 있는 값이나 조건이라면, useEffect 없이 바로 처리하는 것이 더 안전하고 효율적이다.

React 공식 문서인 You Might Not Need an Effect에서는 useEffect를 과하게 쓰지 말아야 할 사례들을 소개하고 있다.. 그중 내가 실제로 실수했거나, useEffect를 줄여볼 수 있는 다섯 가지 상황을 정리해보겠다.

1. 단순 계산만 하고 싶을 때
(난 예전에 이런 상황에서 무조건 useEffect를 썼다...)

// 이렇게 useEffect로 상태 바꾸지 말고...
useEffect(() => {
  setTotal(price * quantity);
}, [price, quantity]);

// 그냥 계산해버리면 됨
const total = price * quantity;

계산만 필요한 경우에는 상태나 effect 없이 렌더링 중에 바로 처리하는 게 더 직관적이고 효율적이다.

2. 조건부 렌더링을 하고 싶을 때

// 이렇게 하는 건 불필요함
useEffect(() => {
  if (isChecked) {
    setShowDelete(true);
  }
}, [isChecked]);

// 랜더링 중에 조건문으로 쓰면 됨
{isChecked && <DeleteButton />}

렌더링 시점에 이미 판단 가능한 조건은 useEffect 없이 JSX 조건문으로 해결하는 게 더 깔끔하다.

3. 파생 상태 관리
다른 상태나 props로부터 계산된 값을 상태로 관리하려 할 때.

// 이렇게 상태로 저장할 필요가 없음
useEffect(() => {
  setFullName(firstName + ' ' + lastName);
}, [firstName, lastName]);

// 그냥 계산해서 사용하면 됨
const fullName = `${firstName} ${lastName}`;

다른 값으로부터 계산되는 값은 상태로 저장하지 말고 직접 계산해서 쓰는 것이 더 안전하다. 따로 저장해두면 상태 간 불일치나 렌더링 지연 등의 버그로 이어질 수 있다.

4. 사용자 이벤트 처리
버튼 클릭, 폼 제출 등 사용자 이벤트에 반응해야 할 때

useEffect(() => {
  if (clicked) {
    fetchData();
  }
}, [clicked]);

이렇게 사용하지 않고

const handleClick = () => {
  fetchData();
};

return <button onClick={handleClick}>Fetch Data</button>;

클릭, 입력 등 사용자의 행동에 반응하는 작업은 상태로 우회하지 말고
이벤트 핸들러에서 직접 처리하는 게 명확하고 안전하다.

5. 렌더링을 위한 데이터 변환
렌더링 전에 데이터를 필터링하거나 정렬해야 할 때.

//필터링된 값을 상태로 관리할 필요는 없음
useEffect(() => {
  setFilteredItems(items.filter(item => item.active));
}, [items]);

//그냥 계산된 값으로 사용하면 된다
const filteredItems = items.filter(item => item.active);

렌더링에 필요한 데이터 변환(필터링, 정렬 등)은 상태로 저장하지 말고, JSX 안에서 바로 계산하는 게 성능상 더 낫고 코드도 간결하다.

마무리

위와 같은 상황들은 useEffect를 쓰는 순간 오히려 복잡도와 버그 가능성을 높인다. 리액트는 렌더링 중 계산이 가능하도록 설계돼 있고, 대부분의 UI 동작은 그 흐름 안에서 해결할 수 있다. 정말 "렌더링 이후"에 해야만 하는 외부 작업이 아니라면, useEffect는 쓰지 않는 것이 더 안전한 선택일 수 있다.

공식문서에는 "컴포넌트를 순수하게 유지하기" 목차에서도

"가능하면 렌더링만으로 로직을 표현하고, 이벤트 핸들러에 넣을 수 없는 부수 효과(side effect)가 필요할 때만 마지막 수단으로 useEffect를 사용하라"

라고 말하고 있다.

결론

렌더링 중에 해결할 수 있는 일이라면 과감하게 useEffect를 사용하지 말고, 정말 렌더링 이후에 실행돼야 하는 외부 작업에만 useEffect를 사용하자!

profile
🏖️ ㅤㅤ 🏊‍♂️

0개의 댓글