Suspense
는 데이터 로딩의 복잡성을 혁신적으로 줄여주었지만, 개발자들이 데이터를 가져오는 방식 자체는 여전히 useEffect
와 useState
의 조합에 크게 의존하고 있었다. 특히 클라이언트 컴포넌트에서 데이터를 가져와야 할 때, 그 익숙한 보일러플레이트는 여전히 건재했다.
// Post.client.js
function Post({ id }) {
const [post, setPost] = useState(null);
useEffect(() => {
fetchPost(id).then(setPost);
}, [id]);
if (!post) {
return <Spinner />;
}
return <h1>{post.title}</h1>;
}
“이것도 여전히 복잡해.”
로렌 탄은 이 코드를 보며 말했다. 그녀는 더 근본적인 단순함을 추구했다.
“async/await
가 자바스크립트에 처음 도입되었을 때를 생각해봐요. Promise 체인을 얼마나 단순하게 만들었죠? 우리는 컴포넌트 안에서도 그런 경험을 제공해야 합니다. 마치 비동기 코드가 동기 코드인 것처럼 보이게 만들어야 해요.”
그녀의 비전은 명확했다. 개발자가 컴포넌트 본문에서 직접 비동기 작업의 결과를 받을 수 있게 하는 것. 하지만 async
키워드를 React 컴포넌트 함수에 직접 붙이는 것은 수많은 부작용을 낳기 때문에 불가능했다. React의 렌더링 생명주기와 맞지 않았다.
팀은 이 문제를 해결하기 위해 새로운 훅(Hook)을 구상하기 시작했다. 그 훅의 역할은 단 하나, ‘Promise를 풀어서 그 결과값을 반환하는 것’이었다. 만약 Promise가 아직 처리 중(pending)이라면? 컴포넌트 렌더링을 일시 중단시키고 가장 가까운 Suspense
에 제어권을 넘기면 된다.
수많은 논의 끝에, 가장 단순하고 강력한 이름의 훅이 탄생했다. 바로 use
였다.
use
훅은 Promise를 인자로 받는다. 그리고 마법처럼, 그 Promise가 완료되면 결과값을 반환한다.
조쉬 스토리가 이전의 Post
컴포넌트를 use
훅을 사용해 다시 작성했다.
// Post.client.js (with use hook)
import { use } from 'react';
import { fetchPost } from './api';
function Post({ id }) {
const post = use(fetchPost(id)); // <--- 혁신적인 한 줄
return <h1>{post.title}</h1>;
}
코드를 본 팀원들은 잠시 말을 잃었다. useState
, useEffect
, 그리고 로딩 상태를 위한 조건문까지. 그 모든 보일러플레이트가 단 한 줄의 코드로 대체되었다.
const post = use(fetchPost(id));
이 한 줄은 이렇게 읽혔다. “fetchPost(id)
라는 Promise의 결과값이 필요해. 준비될 때까지 기다렸다가, 준비되면 post
변수에 담아줘. 기다리는 동안의 로딩 화면은 내가 신경 쓸 테니, 넌 신경 쓰지 마.” (여기서 ‘나’는 React의 Suspense
다.)
이것은 훅의 재발견이었다.
use
는 useState
나 useEffect
처럼 컴포넌트에 새로운 기능을 ‘추가’하는 훅이 아니었다. 오히려 자바스크립트의 비동기 처리라는 기본 개념을 React의 렌더링 생명주기 안에 가장 자연스럽게 녹여내는 ‘통합’의 훅이었다.
개발자는 더 이상 비동기 데이터가 ‘언제’ 도착하는지 신경 쓸 필요가 없었다. 그저 ‘무엇이’ 필요한지만 선언하면 그만이었다. use
훅과 Suspense
의 조합은 비동기 코드를 동기 코드처럼 읽고 쓸 수 있게 만드는, 개발자 경험의 혁명적인 도약이었다.
복잡성은 사라지고, 컴포넌트의 본질적인 목적, 즉 데이터를 받아 UI를 그리는 것만이 코드에 선명하게 남았다. React 19는 이 use
훅을 통해, 개발자들에게 가장 직관적이고 강력한 데이터 페칭 경험을 선물할 준비를 마쳤다.