커스텀 훅(Custom Hook)의 위대한 발견

292025년 09월 13일3

useWindowWidth가 성공적으로 작동하는 것을 확인한 순간, 회의실은 짧은 정적에 휩싸였다. 그 정적은 이내 낮은 탄성으로, 그리고 점차 커지는 흥분으로 바뀌었다. 팀원들은 자신들이 방금 목격한 것이 무엇을 의미하는지 서서히 깨닫고 있었다.

소피가 먼저 입을 열었다. 그녀의 목소리에는 놀라움이 가득했다.
“잠깐만요. 그럼 이제, Header 컴포넌트나 Sidebar 컴포넌트에서도 저 useWindowWidth 함수를 호출하기만 하면 된다는 건가요?”

댄은 말없이 고개를 끄덕이며, 코드를 추가로 작성했다.

function Header() {
  const width = useWindowWidth();
  return (
    <header>
      {width < 600 && <button>Menu</button>}
      {/* ... */}
    </header>
  );
}

function Sidebar() {
  const width = useWindowWidth();
  return <aside style={{ width: width < 600 ? 80 : 240 }}>...</aside>;
}

더 이상 복사-붙여넣기는 없었다.
창 너비를 추적하는 상태 로직은 useWindowWidth라는 단 하나의 함수 안에 완벽하게 캡슐화되었다. 그리고 그 로직이 필요한 모든 컴포넌트는, 그저 그 함수를 호출하여 결과값만 사용하면 되었다.

이것이 바로 그들이 오랫동안 찾아 헤매던 성배였다.
상태 로직을, UI와 분리된 채, 순수한 함수처럼 자유롭게 재사용하는 방법.

세바스티안의 얼굴에 드물게도 환한 미소가 떠올랐다.
“우리가 해냈군요. 이것이 HOC와 렌더 프롭의 완벽한 대체재입니다.”

그의 말이 핵심을 찔렀다.
HOC는 컴포넌트 트리를 복잡하게 만드는 ‘래퍼 지옥’을 만들었다.
렌더 프롭은 JSX 코드를 옆으로 길어지게 하는 ‘콜백 피라미드’를 만들었다.

하지만 이 새로운 방식은 아무런 부작용도 없었다.
컴포넌트 트리는 깨끗했다. JSX 코드도 간결했다.
마치 npm에서 유용한 라이브러리를 import해서 쓰듯, 개발자는 이제 상태 로직을 함수 호출 하나로 가져다 쓸 수 있게 된 것이다.

댄은 이 새로운 종류의 함수에 이름을 붙여야겠다고 생각했다.
useState, useEffect처럼 use라는 접두사로 시작하며, 다른 훅들을 조합해서 만드는 함수.
그는 이것을 ‘커스텀 훅(Custom Hook)’이라고 부르기로 했다.

“이건… 혁명입니다.”
한 주니어 개발자가 경외감에 찬 목소리로 중얼거렸다.

그의 말은 과장이 아니었다.
커스텀 훅의 발견은 리액트가 마주했던 세 개의 거대한 벽 중 첫 번째, ‘상태 로직 재사용의 어려움’을 완벽하게 무너뜨렸다.

  • useStatethissetState의 복잡성을 해결했다.
  • useEffect는 생명주기 메서드의 혼란과 뒤섞인 관심사 문제를 해결했다.
  • 그리고 이 둘을 조합한 ‘커스텀 훅’은 로직 재사용 문제를 해결했다.

지난 몇 달간 그들을 괴롭혔던 모든 문제의 조각들이, ‘훅(Hook)’이라는 단 하나의 개념 아래에서 눈 녹듯이 사라지고 있었다.

이것은 우연한 발견이 아니었다.
클래스의 문제를 해결하기 위해 함수에 상태를 부여하려 했던 시도(useState).
흩어진 로직을 모으기 위해 부수 효과를 분리하려 했던 시도(useEffect).
그리고 이 모든 것이 ‘호출 순서’라는 하나의 규칙 위에서 동작하도록 설계한 결정.

이 모든 과정이 논리적으로 연결되어, 필연적으로 이끌어낸 위대한 발견이었다.

리액트 팀은 이제 확신했다.
그들은 더 이상 기존의 문제들을 해결하는 수준이 아니었다.
그들은 리액트로 애플리케이션을 만드는 방식을, 그리고 UI 개발의 미래를 통째로 바꾸고 있었다. 그들의 손에는 이제 세상을 놀라게 할, 강력하고도 새로운 이야기가 들려 있었다.