리액트를 조금 공부하고 나면 비슷한 useState, useEffect, 이벤트 처리 코드가 여러 컴포넌트에 반복되기 시작합니다. 이때 많은 사람이 “이걸 컴포넌트로 빼야 하나?”라고 고민하는데, UI가 아니라 로직이 반복된다면 커스텀 훅이 더 자연스러운 해법일 때가 많습니다.
커스텀 훅은 어려운 패턴처럼 보일 수 있지만, 사실은 React 훅을 묶어 재사용 가능한 함수로 만드는 일입니다. 한 번 감을 잡으면 코드 중복을 줄일 뿐 아니라, 컴포넌트가 UI 역할에 더 집중하게 만들어줍니다.
이 글에서는 아래 내용을 정리합니다.
- 커스텀 훅이 필요한 순간
- 컴포넌트와 훅의 차이
- 실전에서 자주 나오는 훅 분리 예시
결론부터 말하면 화면은 다르지만 상태 변화 규칙이 같다면 컴포넌트보다 커스텀 훅을 먼저 떠올리는 편이 좋습니다.
React 커스텀 훅은 무엇인가
커스텀 훅은 use로 시작하는 함수이며, 내부에서 다른 React 훅을 사용할 수 있습니다. 목적은 UI를 재사용하는 것이 아니라 상태와 사이드 이펙트 로직을 재사용하는 것입니다.
예를 들어 창 크기를 추적하는 로직은 여러 컴포넌트에서 반복되기 쉽습니다.
function useWindowWidth() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
function handleResize() {
setWidth(window.innerWidth);
}
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return width;
}
이제 필요한 컴포넌트는 const width = useWindowWidth();처럼 사용할 수 있습니다.
언제 커스텀 훅을 만들면 좋을까
아래 상황이라면 훅으로 분리할 가치가 있습니다.
useState와useEffect조합이 여러 파일에 반복된다- 특정 기능의 상태 전환 규칙이 여러 화면에서 같다
- UI는 다르지만 로딩, 에러, 재시도 흐름이 비슷하다
- 한 컴포넌트 안의 로직이 너무 길어 읽기 어렵다
반대로 JSX 구조까지 함께 재사용해야 한다면 컴포넌트가 더 적절할 수 있습니다. 핵심 차이는 재사용 대상이 “화면”인지 “로직”인지입니다.
컴포넌트와 커스텀 훅은 어떻게 다를까
컴포넌트는 화면을 반환합니다. 훅은 화면을 반환하지 않고 값과 함수, 상태를 반환합니다.
예를 들면 아래처럼 나눠서 생각할 수 있습니다.
Modal: 실제 레이어 UI를 그리는 컴포넌트useModal: 열기, 닫기, 토글 상태를 관리하는 훅
function useModal() {
const [open, setOpen] = useState(false);
return {
open,
openModal: () => setOpen(true),
closeModal: () => setOpen(false),
toggleModal: () => setOpen((prev) => !prev),
};
}
이렇게 분리하면 여러 화면에서 같은 모달 상태 로직을 재사용하면서도, 실제 UI 모양은 다르게 가져갈 수 있습니다.
어떤 훅이 입문자에게 좋은 연습일까
처음에는 너무 범용적인 훅을 만들기보다, 작은 반복을 정확하게 줄이는 연습이 좋습니다. 예를 들면 아래 주제들이 적당합니다.
useToggleuseInputuseDebounceuseFetchPostsuseModal
이런 훅들은 로직 경계를 훈련하기 좋고, state, props, useEffect 개념도 자연스럽게 묶어서 복습하게 해줍니다.
자주 하는 실수
1. 훅을 너무 범용적으로 만들기
처음부터 옵션이 많은 거대한 훅을 만들면 내부 분기가 많아지고 오히려 이해하기 어려워집니다. 처음에는 구체적인 문제 하나를 해결하는 훅이 더 좋습니다.
2. 훅 안에서 UI까지 같이 감추려 하기
버튼 문구, 마크업 구조, 스타일 클래스까지 훅이 알게 되면 책임이 섞입니다. 훅은 로직에 집중하고, UI는 컴포넌트가 담당하는 편이 좋습니다.
3. 반환값 설계를 대충 하기
훅이 무엇을 반환하는지 불명확하면 사용하는 쪽이 혼란스러워집니다. 반환값은 “이 훅의 공개 API”라고 생각하면 정리가 쉬워집니다.
작은 예제로 보는 훅 분리
검색 입력값을 디바운스해서 API 호출 빈도를 줄이고 싶다면 아래처럼 시작할 수 있습니다.
function useDebounce(value, delay = 300) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debouncedValue;
}
이 훅 자체는 단순하지만, 안에는 state, effect, cleanup, 의존성 배열 개념이 모두 들어 있습니다. 그래서 커스텀 훅은 리액트 개념을 엮어서 훈련하기 좋은 주제이기도 합니다.
공부할 때 체크할 질문
커스텀 훅을 만들기 전에 아래를 확인해보세요.
- 반복되는 것은 UI인가, 로직인가?
- 이 로직을 이름으로 설명할 수 있는가?
- 훅이 반환해야 할 최소 API는 무엇인가?
- effect와 cleanup 책임이 훅 안에서 자연스럽게 닫히는가?
- 훅 하나가 너무 많은 일을 하지는 않는가?
이 질문을 통과하면 훅은 추상화가 아니라 이해를 돕는 구조가 됩니다.
FAQ
Q. 커스텀 훅 안에서 다른 훅을 써도 되나요?
그렇습니다. React 훅 규칙만 지키면 됩니다.
Q. 훅 하나를 파일로 분리하는 기준이 있나요?
다른 컴포넌트에서도 쓸 가능성이 있거나, 현재 파일을 읽기 어렵게 만들 정도로 길어졌다면 분리를 고려할 만합니다.
Q. 훅이 많아지면 오히려 추적이 어려워지지 않나요?
가능성은 있습니다. 그래서 작은 훅 여러 개보다, 이름이 분명하고 책임이 선명한 훅을 만드는 것이 중요합니다.
Read Next
- effect와 의존성 배열 감각이 아직 애매하다면 React useEffect 가이드를 먼저 다시 보는 것도 좋습니다.
- 메모이제이션과 불필요한 리렌더를 어떻게 봐야 하는지 궁금하다면 React 렌더링 최적화 가이드를 이어서 읽어보세요.
심사 대기 중에는 광고 대신 관련 가이드를 먼저 보여줍니다.
먼저 읽어볼 가이드
검색 유입이 많은 핵심 글부터 이어서 보세요.
- 미들웨어 트러블슈팅 가이드: Redis vs RabbitMQ vs Kafka 개발자를 위한 미들웨어 트러블슈팅 허브 글입니다. Redis, RabbitMQ, Kafka 중 어떤 증상부터 먼저 봐야 하는지와 어떤 문제 패턴이 각 시스템에 가까운지 정리합니다.
- Kubernetes CrashLoopBackOff: 먼저 볼 것들 startup failure, probe, config, resource limit 관점에서 CrashLoopBackOff를 어떻게 나눠서 봐야 하는지 정리한 가이드입니다.
- Kafka consumer lag가 계속 늘 때: 트러블슈팅 가이드 Kafka consumer lag가 계속 늘어날 때 무엇부터 봐야 하는지 정리합니다. poll 주기, 처리 속도, rebalance, consumer 설정까지 실전 기준으로 다룹니다.
- Kafka Rebalancing Too Often 가이드 Kafka consumer group에서 rebalance가 너무 자주 일어날 때 membership flapping, poll timing, protocol, assignment churn을 어떤 순서로 봐야 하는지 설명하는 실전 가이드입니다.
- Docker container가 계속 재시작될 때: 먼저 확인할 것들 exit code, command failure, environment mistake, health check 관점에서 Docker restart loop를 푸는 실전 가이드입니다.
심사 대기 중에는 광고 대신 관련 가이드를 먼저 보여줍니다.