당신의 커스텀 훅은 잘 정돈된 '공구함'인가요?
좋은 커스텀 훅 설계는 대체 뭘까,,,

리액트를 활용해 개발을 하다보면 큰 고민에 빠지곤 한다. UI 컴포넌트 내부에 수없이 혼재되어 있는 비즈니스 로직이 UI 컴포넌트라는 명시적 기능을 훼손 시키는 것은 물론 재사용 불가능하거나 과도한 응집성으로 유지보수가 용이하지 못한 상태를 마주하기 때문이다.
비단 이런 고민은 리액트의 시대에 들어서서 나온 건 아니다. Container - Presentational 패턴과 같이 UI와 로직을 분리시키려는 작업은 항상 있어 왔고 Headless UI 패턴과 같이 UI를 제거한 로직만을 위한 패턴도 존재해왔다.
하지만 리액트 종속적인 비즈니스 로직의 경우 위 패턴보다 Custom Hooks 패턴 과 같이 커스텀 훅을 활용하는 경우가 잦다.
커스텀 훅이란
Custom Hooks : React의 기본 훅(Hook)을 조합하여 만든, 재사용 가능한 로직을 캡슐화한 함수
사실 커스텀 훅 자체는 그다지 어려운 개념은 아니다.
그저 리액트의 훅을 활용해 만들어진 로직을 use 접두사를 붙인 훅 함수로 만들어 추출해 사용하는 것 뿐이다.
사용법 역시 정말 ‘훅을 다루듯’ 사용하면 된다.
하지만 커스텀 훅을 활용하다보면 커스텀 훅을 분리하고 만드는 게 어려운 게 아니라 얼만큼의 책임을 부여하고 어느 수준의 규모로 관리해야할 지 정하는 게 어렵다. 프로젝트의 규모는 점진적으로 증가하기 마련이기에 최초 설계로 깔끔하게 만들어졌을 지언정 가까운 미래에는 사용성이 떨어지는 경험을 하게 된다.
// 간단한 커스텀 훅 사용 예시
// hooks/useExample.ts
import { useState } from 'react';
export const useExample = (initialValue: number = 0) => {
const [count, setCount] = useState(initialValue);
const increment = () => {
if (count < 10) setCount((prev) => prev + 1);
else alert('최대 숫자는 10입니다!');
};
const decrement = () => {
if (count > 0)setCount((prev) => prev - 1);
};
return {
count,
increment,
decrement,
};
};
// components/CounterComponent
import { useExample } from '../hooks/useExample';
const CounterComponent = () => {
// 훅을 호출하여 필요한 기능만 꺼내 씀 (캡슐화)
const { count, increment, decrement } = useExample(0);
return //* UI *//
};
export default CounterComponent;
좋은 커스텀 훅 만들기
위에서 언급한 것 처럼 커스텀 훅을 잘 만들기는 쉬우나 시간이 흘러 변색될 가능성이 높다. 그렇기에 변색 되더라도 목적성을 잃지 않도록 접근하는 것이 가장 베스트라고 생각한다.
커스텀 훅은 일종의 정리함에 비유할 수 있다
우리는 갖가지 물건/도구를 정리함에 넣어두고는 한다.
그리고 내부의 물건을 인식하기 쉽게 “공구류”, “건전지류”, “충전용품류” 등 정리함에는 각 내용물에 맞게 이름표를 붙인다. 알맞게 분류를 해 넣었다면 그 안에 어떤 게 있을 지는 쉽게 파악할 수 있다.
커스텀 훅도 마찬가지다. useCounter 라는 훅이 있다면 그 속에는 count, increment, decrement 와 같이 관련된 값/함수를 가지며 반환한다.
이 때 fetcher와 같이 Counter와 관계 없는 값을 반환한다면 이 훅은 나쁜 훅이 된다. 정리 정돈에 실패한 셈이니까.

그렇기에 커스텀 훅도 하나의 작은 폴더다
폴더 구조를 만들어 나갈 때 기능 단위로 나눌 것인 지, 도메인/서비스 단위로 나눌 것인 지 많은 고민을 한다. 그 와중에 전역적으로 활용되는 소스코드인 지 지역적으로 사용되는 소스코드인 지 고민하며 정리한다.
커스텀 훅도 동일하다. 특정 컴포넌트에서만 사용되는 지 전역에서 재사용 가능한 지에 따라 폴더를 생성하고 개발한다. 큰 눈 뜨고 보면 커스텀 훅 자체가 여러 비즈니스 로직을 담은 하나의 폴더처럼 느껴진다. 그런 이유로 폴더 구조의 관점에서 커스텀 훅의 위치를 결정하면 한결 편하다.

질 좋은 도구로 채워넣어야 할 때
정리함을 만들었다면 그 정리함을 채워넣어야한다. 정확히는 무엇은 꺼내올 수 있을 지를 정의해야한다. 커스텀 훅은 기본적으로 추상화를 추구한다. 때문에 사용자 입장에서는 use/* 정리함과 그 속에 있는 반환값, 이 두가지만 생각하고 활용한다. 그만큼 내부 로직은 추상화된다.
도구 상자가 있다면 그 속에는 드라이버가 있을 것을 기대하지 철광석 덩어리와 플라스틱 원료가 있을 것을 기대하지 않는다. 심지어 원재료만 있으면 그 기능조차 할 수 없다. 때문에 개발자는 정리함의 주제에 맞는 온전한 완제품을 전달하는 것에 집중해야한다.
잘만들어진 훅은 내부 로직을 몰라도 전혀 불편함이 없다. 그 예시는 리액트팀에서 제공해주고 있다. useState와 useEffect 등이 그 예시이다.

(Optional) 다른 정리함의 도구를 활용하기
가령 공구류를 모아둔 보관함이 있고 키보드 조립을 위한 부품 보관함이 있다고 하자. 이 보관함의 도구들을 활용해 키보드를 만들 수 있다. 하지만 키보드를 만들기 전 PCB와 보강판을 스위치를 통해 결합하고 이렇게 결합된 부품을 키보드 하우징에도 결합해야한다. 훅에 대입하면 키보드 조립 훅에는 공구 훅과 키보드 부품 훅을 불러와 내부 로직으로 활용해야한다는 말이며 이를 합성 이라고 통칭한다.
이런 유연한 관계를 만들기 위해서는 훅의 역할에 계층성이 부여되어야 하는 것이며 이는 초기 설계 단계에서 가장 최종적으로 필요한 기능의 훅을 만들고 분리해 나가는 식으로 리팩토링을 하거나 여러 기초적인 훅들을 만들어놓고 활용하며 개발하는 방법 등 다양한 접근법이 존재한다.
하지만 결론적으로는 관심사의 분리가 잘 이루어진 개발이라면 큰 의식하지 않더라도 이미 완성되어있을 가능성이 높다.
마치며
커스텀 훅은 단순히 비즈니스 로직을 분리해 숨기는 공간이 아니다.
나도 동료도 쓸 수 있는 범용의 정리함을 만드는 것과 같다.
일상에서는 데스크나 수납장을 업무 효율성을 높이는 방향으로 세팅하는 법에 대해서 얘기하는 유튜브 영상을 보기도 하고 개발에서는 더 나은 폴더 구조를 통해 개발효율성 높이기 따위의 영상을 보기도 한다. 그런 관심사를 가지고 추구함에도 커스텀 훅 코드를 열어보면 아찔해지곤 할 때가 있다. 먼 곳에서 업무효율을 개선하고자 하는 내 모습도 함께 보게 된다.
그러니 이제는 가까운 곳에서부터 정리정돈의 효율을 높여보자.


![[트러블슈팅] 서버를 터뜨린 쟈그마한 함수](/_next/image?url=https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1748357643167%2Ffc68d237-0bfe-4f79-8e28-15fd98c5710d.png&w=3840&q=75)
![[기술 서적 후기 및 요약] 쏙쏙 들어오는 함수형 코딩](/_next/image?url=https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1743129459556%2F14a7d741-d22e-4622-98e4-1857e449821e.png&w=3840&q=75)
![[Daily Flow 1.] Validator 리팩토링](/_next/image?url=https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1741161717995%2Fa62693a8-0f24-4dc9-8d80-cefe541ed663.png&w=3840&q=75)