React Hook: 함수 컴포넌트의 상태와 로직 재사용
한눈에 보기
React Hook은 함수 컴포넌트에서 상태 관리와 사이드 이펙트 처리를 가능하게 하는 함수들의 모음이다. 훅의 등장 배경, 호출 순서 기반 동작 원리와 규칙, 커스텀 훅을 통한 로직 재사용까지 면접 관점에서 정리한다.
최종 업데이트 2026.09.17
React Hook(리액트 훅)은 함수 컴포넌트에서 상태 관리와 사이드 이펙트(side effect) 처리 같은 React의 핵심 기능을 사용할 수 있게 해 주는 특수한 함수들의 모음이다. React 16.8에서 도입된 이후 클래스 없이도 상태 보관, 생명주기 대응, 로직 재사용이 가능해지면서 함수 컴포넌트가 사실상의 표준 작성 방식으로 자리 잡았다. 모든 훅은 use로 시작하는 이름을 가지며, 렌더링마다 호출 순서가 같아야 한다는 규칙 위에서 동작한다.
훅이 등장한 배경
훅 이전의 React에서는 상태를 가지려면 클래스 컴포넌트를 써야 했다. 클래스 방식에는 고질적인 문제가 있었다. this 바인딩을 실수하기 쉬웠고, 구독과 해제처럼 하나의 관심사에 속하는 코드가 componentDidMount와 componentWillUnmount 같은 여러 생명주기 메서드에 흩어졌다. 상태 로직을 재사용하려면 고차 컴포넌트(Higher-Order Component)나 렌더 프롭(render props) 패턴이 필요했는데, 이런 방식은 컴포넌트 트리를 불필요하게 깊게 만드는 이른바 래퍼 지옥(wrapper hell)을 낳았다.
훅은 이 문제들을 함수 호출로 푼다. 상태와 이펙트를 관심사 단위로 한곳에 모아 쓸 수 있고, 그 묶음을 커스텀 훅이라는 일반 함수로 추출하면 컴포넌트 구조를 바꾸지 않고도 로직을 재사용할 수 있다.
자주 쓰는 내장 훅
| 훅 | 역할 |
|---|---|
| useState | 상태 값과 갱신 함수를 제공한다 |
| useEffect | 렌더링 이후 타이머, 구독 같은 외부 시스템과 동기화한다 |
| useRef | 렌더링을 일으키지 않는 값 보관과 DOM 참조에 쓴다 |
| useMemo, useCallback | 계산 결과와 함수를 캐싱해 불필요한 재계산과 재렌더링을 줄인다 |
| useContext | 컨텍스트 값을 구독한다 |
| useReducer | 복잡한 상태 전이를 리듀서 함수로 관리한다 |
| useTransition | React 18에서 긴급하지 않은 상태 갱신을 표시해 화면 응답성을 지킨다 |
다음은 useState와 useEffect를 함께 쓰는 전형적인 예시다. 정리(cleanup) 함수를 반환하는 부분이 핵심이다.
import { useEffect, useState } from 'react';
function Clock() {
const [now, setNow] = useState(() => new Date());
useEffect(() => {
const id = setInterval(() => setNow(new Date()), 1000);
return () => clearInterval(id);
}, []);
return <time>{now.toLocaleTimeString()}</time>;
}
useEffect가 반환하는 함수는 컴포넌트가 언마운트되거나 의존성이 바뀌어 이펙트가 다시 실행되기 직전에 호출되어 자원을 정리한다.
훅의 동작 원리와 호출 규칙
React는 컴포넌트 인스턴스에 해당하는 파이버(Fiber) 노드에 훅 상태를 연결 리스트 형태로 저장하고, 렌더링 때마다 호출 순서에 따라 저장된 값을 차례로 꺼내 온다. 훅 호출에는 이름 같은 식별자가 없으므로 호출 순서가 곧 식별자 역할을 한다. 여기서 두 가지 규칙이 나온다. 첫째, 훅은 조건문, 반복문, 중첩 함수 안이 아니라 컴포넌트 최상위에서만 호출해야 한다. 둘째, 훅은 함수 컴포넌트나 다른 커스텀 훅 안에서만 호출해야 한다. 조건에 따라 훅 호출을 건너뛰면 다음 렌더링에서 순서가 어긋나 엉뚱한 상태가 연결되는 버그가 생긴다.
커스텀 훅으로 로직 재사용
커스텀 훅은 use로 시작하는 이름을 가진 일반 함수이며, 내부에서 다른 훅을 호출한다는 점이 유일한 특징이다. 다음은 입력값이 잠시 멈춘 뒤에만 반영되는 디바운스(debounce) 훅의 예시다.
import { useEffect, useState } from 'react';
function useDebouncedValue(value, delay) {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const id = setTimeout(() => setDebounced(value), delay);
return () => clearTimeout(id);
}, [value, delay]);
return debounced;
}
주의할 점은 커스텀 훅이 공유하는 것은 상태 자체가 아니라 상태를 다루는 로직이라는 사실이다. 훅을 호출하는 컴포넌트마다 독립적인 상태가 새로 만들어지므로, 여러 컴포넌트가 같은 값을 공유해야 한다면 컨텍스트나 외부 스토어가 필요하다.
주의할 점
- 의존성 배열을 빠뜨리거나 잘못 채우면 오래된 값을 참조하는 스테일 클로저(stale closure) 문제가 생긴다. 린트 규칙 exhaustive-deps를 존중하는 편이 안전하다.
- useMemo와 useCallback은 최적화 수단이지 정확성 수단이 아니다. 성능 측정 없이 모든 값을 감싸면 코드만 복잡해진다.
- 렌더링 중에 계산할 수 있는 파생 값을 useEffect와 별도 상태로 관리하면 렌더링이 한 번 더 일어난다. 파생 값은 렌더링 중에 바로 계산한다.
- React 18의 StrictMode는 개발 모드에서 이펙트를 마운트 직후 한 번 더 실행한다. 이를 정리 함수가 올바른지 검증하는 기회로 삼는다.
면접에서 이렇게 물어본다
- 훅을 조건문 안에서 호출하면 안 되는 이유를 React 내부 저장 구조와 연결해 설명해 보라.
- useEffect의 의존성 배열이 빈 배열일 때, 아예 없을 때, 값이 있을 때 실행 시점이 어떻게 다른가.
- 커스텀 훅을 여러 컴포넌트에서 사용할 때 상태가 공유되는가, 로직만 공유되는가.
- 클래스 컴포넌트의 생명주기 메서드는 함수 컴포넌트에서 어떤 훅 조합으로 대체되는가.
- useMemo와 useCallback을 언제 도입하고 언제 도입하지 말아야 하는가.
React / Vue에서 같이 읽기
- React Fragment: 불필요한 래퍼 없이 요소를 묶는 방법React Fragment는 여러 자식 요소를 추가 DOM 노드 없이 하나로 묶어 반환하게 해 주는 문법 요소다. 하나의 루트 요소 제약을 래퍼 없이 해결해 DOM 깊이와 CSS 레이아웃 문제를 예방하는 원리와 key 사용법을 정리한다.
- React와 Spring Boot 연동 구조: CORS, 프록시, 배포 전략React SPA와 Spring Boot REST API 서버를 분리해 구성하는 아키텍처를 다룬다. 개발 환경의 CORS와 프록시 설정, 정적 빌드 산출물의 통합 서빙과 분리 서빙 전략, 새로고침 404 해결까지 원리 중심으로 설명한다.
- Vue 3와 Firebase 이메일 인증: 토큰 기반 인증 흐름Firebase Authentication은 이메일 인증을 서버 구현 없이 제공하는 관리형 인증 서비스다. ID 토큰과 리프레시 토큰의 흐름, Vue 3 컴포저블 통합, 라우터 가드와 세션 유지, 보안 주의점을 원리 중심으로 정리한다.
- Vue 3와 Firebase 소셜 로그인: OAuth 위임과 연동 원리소셜 로그인의 바탕인 OAuth 2.0 권한 위임 원리와 Firebase가 소셜 제공자를 중개하는 인증 흐름을 다룬다. 팝업과 리다이렉트 방식 비교, Vue 3 구현, 계정 충돌 처리까지 정리한다.
이 개념, 내 코드에서는 어떻게 물어볼까요
데브핏(DevFit)에 채용공고 URL을 붙여 넣고 레포지토리를 연결하면, 내가 쓴 커밋에서 예상 질문과 어필 포인트를 만듭니다. 하루 10회까지 무료입니다. 합불 판정이나 지원자 랭킹은 만들지 않고, 최종 판단은 사람이 합니다.
공고 넣고 시작하기