커뮤니티에서 쏟아지는 화려한 커스텀 훅 라이브러리들을 보며, 이제 막 훅을 배우기 시작한 주니어 개발자 리암(Liam)은 경외감과 함께 약간의 위축감을 느꼈다. 저렇게 복잡하고 멋진 훅들은 특별한 사람들만 만들 수 있는 것이라고 생각했다.
그러던 어느 날, 그에게 ‘무한 스크롤(Infinite Scroll)’ 기능을 구현하는 과제가 주어졌다. 사용자가 페이지의 끝까지 스크롤을 내리면, 다음 페이지의 데이터를 자동으로 불러와 리스트에 추가하는 기능이었다.
리암은 막막했다.
이 기능을 구현하려면 여러 개의 훅을 동시에 다루어야 했다.
- 데이터 목록과 현재 페이지 번호를 저장할
useState. - 데이터를 가져오는 비동기 로직을 처리할
useEffect. - 스크롤 이벤트를 처리할 함수를 기억할
useCallback. - 리스트의 마지막 요소를 감시할
useRef와Intersection Observer.
이 모든 것을 하나의 컴포넌트 안에서 구현하려니, 코드는 금세 복잡해지고 서로 얽히기 시작했다. 그는 자신이 과연 이 과제를 해낼 수 있을지 의심스러웠다.
그때, 댄이 워크숍에서 했던 말이 떠올랐다.
“커스텀 훅은 특별한 것이 아닙니다. 그저 use로 시작하는 함수일 뿐입니다. 여러분이 반복적으로 작성하는 로직이 있다면, 그것을 함수로 추출해보세요.”
리암은 용기를 내기로 했다.
그는 거대한 라이브러리를 만드는 것이 아니라, 오직 자신의 이 문제를 해결하기 위한, 자신만의 작은 훅을 만들어보기로 결심했다.
그는 파일 하나를 새로 만들고, useInfiniteScroll이라는 이름을 붙였다.
그는 먼저, 필요한 상태들을 훅의 내부로 옮겼다.
function useInfiniteScroll(fetchFunction) {
const [items, setItems] = useState([]);
const [page, setPage] = useState(1);
const [loading, setLoading] = useState(false);
// ...
}
이어서, 데이터를 가져오는 로직을 useEffect로 구현했다. 이 효과는 page 번호가 바뀔 때마다 실행되어야 했다.
useEffect(() => {
setLoading(true);
fetchFunction(page).then(newItems => {
setItems(prevItems => [...prevItems, ...newItems]);
setLoading(false);
});
}, [page, fetchFunction]);
가장 까다로운 부분은 스크롤의 끝을 감지하는 로직이었다. 그는 Intersection Observer를 사용하기로 하고, 관찰 대상이 될 마지막 요소를 가리킬 ref를 만들었다. 그리고 이 ref를 외부로 노출시켜, 컴포넌트가 DOM 요소에 연결할 수 있게 했다.
몇 시간의 사투 끝에, 마침내 그의 첫 번째 커스텀 훅이 완성되었다. 여러 개의 훅들이 조합되어, 하나의 의미 있는 기능을 수행하는 작은 기계 장치와도 같았다.
function useInfiniteScroll(fetchFunction) {
// ... (useState, useEffect, useCallback, useRef 로직들) ...
// 훅은 데이터 목록과, 관찰할 요소를 위한 ref를 반환한다.
return { items, loading, targetRef };
}
이제 이 훅을 사용하는 컴포넌트의 코드는 놀랍도록 단순해졌다.
function PostList() {
// 훅을 호출하여 필요한 모든 것을 받는다.
const { items, loading, targetRef } = useInfiniteScroll(fetchPostsAPI);
return (
<div>
{items.map((post, index) => {
// 마지막 게시물이라면, ref를 연결하여 감시한다.
if (items.length === index + 1) {
return <Post key={post.id} post={post} ref={targetRef} />;
}
return <Post key={post.id} post={post} />;
})}
{loading && <p>Loading more posts...</p>}
</div>
);
}
코드를 실행하자, 무한 스크롤 기능이 완벽하게 작동했다.
리암은 자신의 모니터를 보며, 믿을 수 없다는 듯이 미소 지었다.
그는 그저 기존에 있던 기본 훅들을 하나씩 조립했을 뿐인데, useInfiniteScroll이라는 강력하고 재사용 가능한 새로운 부품을 창조해낸 것이다.
그 순간, 리암은 커스텀 훅의 진정한 힘이 ‘창조’가 아니라 ‘조합’에 있다는 것을 온몸으로 깨달았다.
훅은 천재 개발자들의 전유물이 아니었다. 그것은 문제를 더 작은 단위로 나누고, 그 해결책들을 레고 블록처럼 쌓아 올릴 줄 아는 모든 개발자를 위한 도구였다.
그는 자신의 첫 커스텀 훅을 팀에 공유했다. 동료들은 그의 작업에 찬사를 보냈고, 이 훅은 팀의 다른 프로젝트에서도 유용하게 사용되기 시작했다. 리암은 처음으로, 자신이 생태계에 기여했다는 깊은 성취감을 느꼈다.
하지만 이 성취감 뒤에는, 모든 강력한 추상화가 가진 그림자가 드리워져 있었다. 그는 곧, 자신의 훅이 거대해지면서 마주하게 될 새로운 종류의 책임과 문제에 대해 배우게 될 터였다.


