생명주기 메서드의 해체와 재조립

272025년 09월 11일4

useStateuseEffect.
두 개의 훅이 완성되자, 리액트 팀은 경이로운 사실을 깨달았다. 그들은 자신들이 만든 것이 단순히 클래스의 기능을 흉내 낸 것을 넘어, 컴포넌트를 작성하는 방식을 근본적으로 바꾸어 놓았다는 것을.

댄은 팀원들 앞에서, 클래스 시절의 가장 고통스러웠던 문제들을 이 새로운 도구로 어떻게 풀어낼 수 있는지 하나씩 증명해 보이기 시작했다.

그는 화이트보드의 한쪽에 복잡한 클래스 컴포넌트의 생명주기 메서드들을 나열했다.

Before: Class Component

class FriendStatusWithLogging extends React.Component {
  // 1. 상태 관리
  state = { isOnline: null };

  // 2. 생명주기 로직
  componentDidMount() {
    // A. 친구 상태 구독
    ChatAPI.subscribeToFriendStatus(...);
    // B. 페이지 진입 로그 기록
    LoggingAPI.logUserEntered(this.props.friend.id);
  }

  componentWillUnmount() {
    // A. 친구 상태 구독 해지
    ChatAPI.unsubscribeFromFriendStatus(...);
  }

  // ... (render, handleStatusChange 등)
}

“이 코드를 보세요.” 댄이 말했다. “여기에는 두 가지 관심사가 섞여 있습니다. 하나는 ‘친구 상태를 구독하는 것(A)’, 다른 하나는 ‘사용자 행동을 로깅하는 것(B)’입니다. 그리고 A 로직은 componentDidMountcomponentWillUnmount에 흩어져 있죠.”

그는 화이트보드 반대편에 새로운 코드를 작성했다.

After: Function Component with Hooks

function FriendStatusWithLogging({ friend }) {
  // 1. 상태 관리
  const [isOnline, setIsOnline] = useState(null);

  // 2. 친구 상태 구독 로직 (관심사 A)
  useEffect(() => {
    ChatAPI.subscribeToFriendStatus(friend.id, setIsOnline);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(friend.id, setIsOnline);
    };
  }, [friend.id]);

  // 3. 로깅 로직 (관심사 B)
  useEffect(() => {
    LoggingAPI.logUserEntered(friend.id);
  }, [friend.id]);

  // ... (렌더링 로직)
}

결과는 놀라웠다.
이전에는 뒤섞이고 흩어져 있던 코드들이, 완벽하게 기능별로 재조립되었다.

‘친구 상태 구독’과 관련된 모든 코드(시작, 정리, 그리고 의존성)는 이제 첫 번째 useEffect 블록 안에 캡슐화되었다.
‘로깅’과 관련된 코드는 두 번째 useEffect 블록 안에 명확하게 분리되었다.

더 이상 componentDidMount라는 하나의 바구니에 관련 없는 코드를 쑤셔 넣을 필요가 없었다. useEffect는 필요한 만큼 여러 번 호출할 수 있었고, 각각의 useEffect는 독립적인 하나의 관심사를 책임졌다.

소피가 감탄하며 말했다.
“이제 로깅 기능이 필요 없는 다른 컴포넌트를 만들고 싶다면, 두 번째 useEffect 블록만 그냥 지워버리면 되겠군요. 친구 상태 구독 로직에는 아무런 영향을 주지 않고요.”

정확했다.
코드의 수정과 삭제가 훨씬 안전하고 예측 가능해졌다. 각 기능은 서로에게 영향을 주지 않는 독립적인 모듈처럼 작동했다.

세바스티안은 여기서 더 깊은 의미를 짚어냈다.
“우리는 더 이상 컴포넌트의 ‘생명’을 생각할 필요가 없습니다. ‘Mount’나 ‘Update’ 같은 시간의 흐름을 잊어도 됩니다. 대신, 우리는 이제 ‘동기화’의 관점에서 생각하게 될 겁니다.”

그의 말처럼, 새로운 모델은 개발자의 멘탈 모델을 완전히 바꾸었다.

  • useState: 컴포넌트의 상태를 선언한다.
  • useEffect: 컴포넌트의 상태를 외부 세계와 동기화한다.

이 두 가지만으로 충분했다.
복잡하고 명령적이었던 생명주기 메서드들은, 단순하고 선언적인 두 개의 훅으로 완벽하게 해체되고 재조립되었다.

댄은 화이트보드에 나란히 적힌 ‘Before’와 ‘After’ 코드를 바라보았다.
이것은 단순한 코드 스타일의 변화가 아니었다.
리액트 컴포넌트를 바라보는 관점 자체가 바뀌는, 패러다임의 전환이었다.

하지만 아직 풀어야 할 가장 큰 문제가 남아있었다.
리액트 팀이 이 여정을 시작하게 된 최초의 질문.
‘어떻게 하면 상태 로직을 쉽게 재사용할 수 있을까?’

HOC와 렌더 프롭을 역사의 뒤안길로 사라지게 할, 최후의 한 방이 필요했다. 그리고 그 해답은, 그들이 방금 만들어낸 useEffect의 재조립 능력 속에 이미 숨어 있었다.