— 커스텀 훅을 기능이 아닌 ‘책임 단위’로 나누는 기준
React Hook은 단순히 코드를 줄이기 위한 문법이 아니다.
프로젝트가 커질수록 상태 관리, 로직 재사용, 데이터 흐름 제어의 중심이 된다.
이번 글에서는 “Hook을 언제, 어떤 기준으로 나누면 좋은가?”에 대해 공부한 내용을 정리해보았다.
처음엔 useState, useEffect로 충분하다.
하지만 컴포넌트가 커지면 이런 현상이 생긴다 👇
이때 커스텀 훅(Custom Hook) 은 “로직을 옮겨놓는 함수”가 아니라,
컴포넌트가 가져야 할 책임을 분리하고 독립시키는 도구로 보는 게 중요하다.
“Hook은 코드를 분리하는 수단이 아니라, 책임을 분리하는 수단이다.”
React의 초반 단계에서는 대부분 이렇게 작성한다.
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log("Count changed:", count);
}, [count]);
}
문제는, 기능이 늘어날수록 useEffect가 점점 복잡해지고 “의존성 지옥” 이 시작된다는 것이다.
useEffect(() => {
fetchData();
handleResize();
handleAuthCheck();
}, [token, width, isLoggedIn]);
각 로직이 서로 영향을 주고받으며 렌더링 타이밍, 비동기 처리, 상태 동기화 문제가 꼬이기 시작한다.
이 시점이 바로 커스텀 훅을 도입해야 할 타이밍이다.
많은 개발자들이 처음엔 useLogin, useFetch, useForm 처럼“기능 단위”로 훅을 만든다.
하지만 진짜 좋은 분리는 ‘하나의 책임(Responsibility)’에 집중된 훅이다.
| 잘못된 기준 | 올바른 기준 |
|---|---|
| 비슷한 코드가 반복되니까 묶는다 | 하나의 논리적 책임을 가진다 |
| 여러 사이드이펙트를 한 곳에 몰아넣는다 | 각 훅은 오직 하나의 역할만 수행한다 |
| 훅 내부에서 또 다른 훅을 호출 | 상위 훅이 하위 훅을 조립해 데이터 흐름을 제어 |
예를 들어 useFetch라는 훅은 “데이터 요청 + 에러 핸들링 + 로딩 상태”라는 하나의 책임에 집중되어 있다면 괜찮다.
하지만 여기에 “토큰 갱신”이나 “자동 재시도”까지 들어간다면 이미 역할이 넘친 상태다.
커스텀 훅을 많이 만들다 보면 이런 문제가 생긴다.
“A 훅 안에서 B 훅을 직접 호출하면 안 되나?”
가능은 하지만, 훅 간 결합도가 올라가고 재사용성이 떨어진다.
이럴 때 효과적인 방법은 Controller 훅 패턴이다.
즉, 여러 하위 훅을 조립하고 상위 레벨에서 데이터 흐름을 제어하는 방식이다.
function useController() {
const { data, error, refetch } = useFetch("/api/data");
const { isOpen, toggle } = useModal();
const { value, onChange } = useFormField("");
// 데이터 흐름 조립
useEffect(() => {
if (error) toggle(true); // 에러 시 모달 오픈
}, [error]);
return { data, error, isOpen, toggle, value, onChange, refetch };
}
이렇게 하면 각각의 훅은 하나의 책임에 집중하고,
Controller 훅은 전체 로직을 조율하는 역할만 맡게 된다.
프로젝트 단위에서 커스텀 훅을 설계할 때는 3단계 구조로 나누면 깔끔하다.
여러 훅을 조립해 데이터 흐름을 제어
예:
useDashboard,useAuthFlow
특정 도메인의 상태를 캡슐화
예:
useUserState,useThemeState
순수 기능만 수행, 사이드이펙트 없음
예:
useInterval,usePrevious,useMediaQuery
💡 패턴 정리:
“Controller → State / Utility” 구조로 나누면 데이터 흐름이 명확하고, 유지보수가 쉬워진다.
좋은 훅은 이름만 봐도 무슨 일을 하는지 알 수 있다.
나쁜 훅은 “이 훅이 뭘 하는지 직접 열어봐야” 알 수 있다.
// ❌ 나쁜 예시
useCommonLogic();
useSomething();
// ✅ 좋은 예시
useModalVisibility();
useAuthVerification();
useKeyboardShortcut();
“무엇을 하는가?”가 아니라 “왜 존재하는가?”를 드러내는 네이밍이 좋다.
| 실수 | 문제점 |
|---|---|
| 훅 안에서 상태를 너무 많이 관리 | 책임 불분명, 테스트 어려움 |
| 훅 안에서 훅을 직접 호출 | 결합도 증가, 순환참조 위험 |
| 의존성 배열을 무시하거나 남발 | 렌더링 타이밍 예측 불가 |
| 커스텀 훅 내부에서 DOM 조작 | React의 선언형 철학과 충돌 |
결국 커스텀 훅의 목적은 “로직을 옮기는 것”이 아니라,
상태 → 로직 → UI의 흐름을 명확히 보여주는 것이다.
Hook은 기술이 아니라 사고방식이다.
컴포넌트를 “UI 중심”이 아니라 “데이터 흐름 중심”으로 바라보게 만들어준다.