React 훅은 이름이 use로 시작하는 함수로, 함수 컴포넌트가 React 기능을 쓸 수 있게 해 줍니다. 값을 기억하는 useState, 렌더링 후 코드를 실행하는 useEffect, DOM 노드를 붙잡아 두는 useRef, 공유 데이터를 읽는 useContext 같은 것들입니다. 컴포넌트의 맨 위에서 호출하면 React가 렌더링 사이에 그 값들을 유지합니다.
여기서는 네 개의 훅이 함께 동작합니다. useState는 텍스트를 저장하고, useRef는 버튼이 포커스할 수 있도록 input을 가리키며, useMemo는 query가 바뀔 때까지 필터링된 목록을 유지하고, useEffect는 query가 바뀐 렌더링마다 로그를 남깁니다. an을 입력하고 Console을 보세요. 목록이 업데이트된 뒤 키 입력마다 한 줄씩 나옵니다.
훅이 존재하는 이유
React 16.8 이전에는 state나 생명주기 코드가 필요한 컴포넌트는 클래스여야 했습니다. 기능 하나의 로직이 componentDidMount, componentDidUpdate, componentWillUnmount에 흩어졌고, 컴포넌트 사이에서 공유하려면 믹스인(클래스 문법에서는 동작하지 않았습니다), 고차 컴포넌트, render props를 써야 했으며, 모두 트리를 추가 계층으로 감쌌습니다.
// The class way: one feature, three methods
class Clock extends React.Component {
state = { now: new Date() };
componentDidMount() {
this.id = setInterval(() => this.setState({ now: new Date() }), 1000);
}
componentWillUnmount() {
clearInterval(this.id);
}
render() {
return <p>{this.state.now.toLocaleTimeString()}</p>;
}
}
훅을 쓰면 같은 기능이 한곳에 있습니다. 이펙트가 타이머를 시작하고, 그 클린업이 타이머를 멈춥니다.
시계를 숨기면 Console에 timer stopped가 출력됩니다. Clock이 화면에서 사라질 때 React가 클린업을 실행한 것입니다. 다시 보여 주면 새 타이머가 시작됩니다. 클래스 컴포넌트는 React 19에서도 여전히 동작하지만, 새 코드는 훅을 사용합니다.
모든 내장 훅
React 19.2의 훅을 컴포넌트에 무엇을 주는지에 따라 묶었습니다. 처음 두 그룹은 매일 쓰게 되고, 나머지는 특정 문제가 생겼을 때 씁니다.
| 훅 | 제공하는 것 | 페이지 |
|---|---|---|
useState | 렌더링 사이에 유지되는 값과 다시 렌더링하는 setter | useState |
useReducer | 리듀서 함수와 dispatch한 액션으로 업데이트되는 state | useReducer |
useContext | props를 넘기지 않고 부모가 제공하는 값 | useContext |
useRef | 렌더링 사이에 유지되는 변경 가능한 상자, 주로 DOM 노드 | useRef |
useImperativeHandle | 부모가 ref를 통해 보는 것을 결정 | forwardRef |
useEffect | 보통 그린 뒤에 실행되는 코드: 타이머, 구독, 데이터 가져오기 | useEffect |
useLayoutEffect | DOM 업데이트 후 그리기 전에 실행되는 코드 | useLayoutEffect |
useInsertionEffect | CSS-in-JS 라이브러리를 위해 레이아웃 이펙트보다 먼저 실행 | |
useEffectEvent | 이펙트가 호출할 수 있고 항상 최신 props와 state를 보는 함수 | useEffect |
useMemo | 렌더링 사이에 계산된 값을 캐시 | useMemo |
useCallback | 렌더링 사이에 함수를 캐시 | useCallback |
useTransition | 업데이트를 긴급하지 않은 것으로 표시하고 대기 중인지 알려 줌 | useTransition |
useDeferredValue | 무거운 렌더링 중에 뒤처져 따라오는 값의 사본 | useDeferredValue |
useId | 서버와 클라이언트에서 일치하는 고유 id | useId |
useSyncExternalStore | React 바깥에 있는 스토어를 구독 | useSyncExternalStore |
useDebugValue | React DevTools에서 커스텀 훅에 붙는 라벨 | |
use | 프로미스나 컨텍스트를 읽으며, 조건문 안에 있어도 됨 | use |
useActionState | 폼 액션이 업데이트하는 state와 대기 플래그 | useActionState |
useOptimistic | 서버가 응답하기 전에 예상 결과를 보여 줌 | useOptimistic |
useFormStatus | 부모 폼의 대기 상태(react-dom 제공) |
use, useActionState, useOptimistic, useFormStatus는 React 19와 함께 등장했고, useEffectEvent는 19.2에서 안정화되었습니다. 목록의 나머지 훅은 모두 React 18에서도 동작합니다.
state는 렌더링을 일으키고, ref는 일으키지 않습니다
모든 훅이 렌더링을 일으키는 것은 아닙니다. useState와 useReducer는 일으킵니다. 값을 바꾸면 React가 컴포넌트를 다시 그립니다. useRef는 current 필드를 자유롭게 바꿀 수 있는 객체를 주며, React는 그것 때문에 다시 렌더링하지 않습니다. 그래서 타이머 id, 이전 값, 화면에 보이지 않는 카운트를 두기에 알맞은 곳입니다.
useReducer는 장바구니를, useState는 메모를 보관합니다. 둘 다 바뀌면 렌더링을 일으키고, 이펙트는 렌더링마다 한 줄씩 기록합니다. 이펙트는 commits.current도 바꾸지만, 그 변경은 스스로 렌더링을 일으키지 않으므로 로그가 반복되지 않습니다. 카운트를 컴포넌트 본문이 아니라 이펙트에서 업데이트하는 이유는, 본문이 렌더링하는 동안 아무것도 바꾸면 안 되기 때문입니다.
훅의 규칙
모든 훅은 두 가지 규칙을 따릅니다.
- 훅은 컴포넌트의 최상위에서 호출하고, 조건문, 반복문, 중첩 함수 안에서는 절대 호출하지 마세요.
- 훅은 함수 컴포넌트나 직접 만든 커스텀 훅에서만 호출하세요.
React는 훅을 이름으로 알지 못합니다. 호출 순서로 알며, 그 순서가 렌더링마다 같기를 기대합니다. if 안의 훅은 그 뒤의 모든 훅을 밀어냅니다. 훅의 규칙 페이지에서 무엇이 깨지고 어떻게 고치는지 보여 줍니다. use는 유일한 예외로, 조건문 안에서 호출해도 됩니다.
첫 번째 커스텀 훅
두 컴포넌트가 같은 상태 로직을 필요로 한다면, 그 로직을 이름이 use로 시작하는 함수로 옮기세요. 그 함수는 다른 어떤 훅이든 호출할 수 있습니다. 그것을 호출하는 각 컴포넌트는 자기만의 state를 받습니다.
두 카운터는 같은 훅을 실행하지만, 하나를 클릭해도 다른 하나는 그대로입니다. 커스텀 훅은 state가 아니라 로직을 공유합니다. useCounter에 decrement 함수와 그것을 호출하는 버튼을 추가하면 두 카운터가 한꺼번에 그 기능을 얻습니다. 더 많은 패턴(useToggle, useDebounce, useLocalStorage)은 커스텀 훅 페이지에 있습니다.
어떤 훅을 써야 할까
- 값이 바뀔 때 화면이 바뀌어야 한다면:
useState, 또는 여러 이벤트가 같은 state를 업데이트한다면useReducer. - React 바깥의 무언가(타이머, 구독, 요청)와 통신해야 한다면:
useEffect. - DOM 노드나 화면에 보이지 않는 값이 필요하다면:
useRef. - 트리 깊숙한 곳의 여러 컴포넌트가 같은 값을 필요로 한다면:
useContext. - 계산 하나 때문에, 또는 메모이제이션된 자식이 다시 렌더링되어서 렌더링이 느리다면:
useMemo와useCallback. React Compiler가 이 메모이제이션을 대신 추가해 줄 수 있지만, 직접 작성하는 방식도 여전히 동작하고 여전히 흔합니다. - 뒤에서 일어나는 업데이트가 무거워서 입력이 버벅인다면:
useTransition또는useDeferredValue.
자주 묻는 질문
React에서 훅이란 무엇인가요?
훅은 이름이 use로 시작하는 함수로, 함수 컴포넌트가 React 기능을 쓸 수 있게 해 줍니다. state에는 useState, 부수 효과에는 useEffect, 컨텍스트에는 useContext, 렌더링을 일으키지 않고 렌더링 사이에 유지되는 값에는 useRef 등을 사용합니다.
React에 훅이 추가된 이유는 무엇인가요?
훅 이전(React 16.8)에는 state와 생명주기 메서드가 클래스 컴포넌트에만 있었고, 상태를 가진 로직을 공유하려면 믹스인, 고차 컴포넌트, render props가 필요했습니다. 훅은 그 로직을 어떤 함수 컴포넌트에서든 호출할 수 있고 커스텀 훅으로 공유할 수 있는 일반 함수에 담습니다.
React에는 훅이 몇 개 있나요?
React 19.2는 react 패키지에 약 스무 개의 내장 훅을, react-dom에 useFormStatus를 제공합니다. 대부분의 앱이 매일 쓰는 것은 몇 개뿐입니다: useState, useEffect, useRef, useContext, useMemo, useCallback.
클래스 컴포넌트에서 훅을 쓸 수 있나요?
아니요. 훅은 함수 컴포넌트와 다른 훅 안에서만 동작합니다. 그래도 클래스 컴포넌트를 함수 컴포넌트 옆에 렌더링할 수 있고, 클래스가 훅을 쓰는 함수 컴포넌트를 렌더링할 수도 있습니다.
커스텀 훅이란 무엇인가요?
이름이 use로 시작하고 다른 훅을 호출하는 직접 만든 함수입니다. 여러 컴포넌트가 같은 상태 로직을 재사용할 수 있게 해 주며, 그것을 호출하는 각 컴포넌트는 여전히 자기만의 별도 state를 받습니다.