use의 탄생 비화

282025년 09월 12일3

use 훅이라는 결과물은 놀랍도록 간결했지만, 그곳에 도달하기까지의 과정은 결코 순탄치 않았다. 그것은 수년간 누적된 개발자들의 고통과, 그 고통을 해결하려는 React Core Team의 집요한 고민이 응축된 결과물이었다.

모든 것의 시작은 useEffect였다.

훅이 처음 도입되었을 때, useEffect는 클래스 컴포넌트의 복잡한 생명주기 메서드들을 대체하는 구원자처럼 여겨졌다. 하지만 비동기 데이터를 가져오는 작업에 사용되면서, useEffect는 점차 새로운 복잡성의 온상이 되어갔다.

로렌 탄은 팀 회의에서 useEffect를 사용한 데이터 페칭 코드의 문제점들을 조목조목 짚었다.

“첫째, 경쟁 상태(Race Condition) 문제입니다.”

그녀는 사용자가 검색어를 빠르게 연속으로 입력하는 시나리오를 예로 들었다.

“사용자가 ‘react’를 입력하고, 곧바로 ‘react 19’를 입력했다고 가정합시다. 두 개의 API 요청이 거의 동시에 출발합니다. 만약 네트워크 상황 때문에 ‘react’에 대한 응답이 ‘react 19’에 대한 응답보다 늦게 도착하면 어떻게 될까요? 화면에는 오래된 검색 결과가 최신 결과를 덮어쓰게 됩니다.”

개발자들은 이 문제를 해결하기 위해 useEffect의 클린업(cleanup) 함수 안에서 요청을 취소하거나, 요청이 유효한지 확인하는 플래그 변수를 사용하는 등 복잡한 방어 코드를 매번 작성해야 했다.

“둘째, 의존성 배열(Dependency Array)의 함정입니다.”

그녀는 useEffect의 두 번째 인자인 배열을 가리켰다. “개발자들은 이 배열에 무엇을 넣어야 할지 항상 헷갈려 합니다. 배열을 비워두면 데이터가 갱신되지 않고, 필요한 모든 값을 넣지 않으면 린터가 경고를 띄우죠. 이로 인해 불필요한 리렌더링이 발생하는 경우가 비일비재합니다.”

이 외에도 isLoading, error 상태를 수동으로 관리하는 번거로움, 서버 사이드 렌더링 환경과의 비호환성 등 useEffect를 사용한 데이터 페칭은 수많은 함정을 내포하고 있었다.

“이건 너무 복잡해.”

로렌은 단호하게 말했다. 그녀의 목소리에는 좌절감과 함께, 더 나은 해결책을 향한 갈망이 담겨 있었다.

“우리는 지금 구멍 난 댐을 손가락으로 막고 있는 것과 같아요. useEffect는 애초에 이런 종류의 비동기 작업을 위해 설계된 도구가 아닙니다. 우리에게는 더 근본적인 해결책이 필요합니다.”

바로 그 순간, use 훅의 초기 아이디어가 싹텄다.

팀의 논의는 ‘만약 React가 Promise의 상태를 직접 이해할 수 있다면?’이라는 질문으로 옮겨갔다. 만약 컴포넌트가 렌더링 중에 Promise를 만나면, React가 그 Promise가 해결될 때까지 렌더링을 ‘멈추고’, 해결된 후에 그 지점부터 ‘다시 시작’할 수 있다면 어떨까?

이것은 useEffect처럼 렌더링이 ‘끝난 후’에 부수 효과를 실행하는 방식과는 완전히 다른 접근이었다. 렌더링 과정 ‘중에’ 비동기 작업이 자연스럽게 통합되는 방식이었다.

그 아이디어를 실현하기 위한 구체적인 인터페이스가 바로 use 훅이었다. use는 React에게 “나는 이 Promise의 결과가 필요하니, 준비될 때까지 렌더링을 잠시 멈춰줘”라는 신호를 보내는 약속의 증표였다.

use의 탄생은 단순히 또 하나의 훅을 추가한 것이 아니었다. 그것은 useEffect라는 잘못 사용되던 도구에 대한 깊은 반성이자, 비동기 데이터 페칭이라는 웹 개발의 본질적인 문제를 해결하기 위한 React의 공식적인 대답이었다. 복잡한 패턴과 함정으로 가득했던 길을 버리고, 가장 직관적이고 안전한 새로운 길을 제시하는, 패러다임의 전환이었다.