조건문, 반복문, 중첩 함수 안에서 훅을 부르지 않습니다. 항상 같은 순서로 실행돼야 합니다.
이 파트에서 다루는 내용
훅에는 두 가지 규칙이 있습니다
React는 훅이 호출되는 순서로 각 상태를 구분합니다. 그래서 호출 순서가 렌더링마다 달라지면 상태가 뒤섞입니다.
규칙은 두 개뿐이고, 대부분의 훅 관련 오류는 이 둘을 어겼을 때 발생합니다.
컴포넌트 또는 다른 훅 안에서만 부릅니다. 일반 함수나 이벤트 핸들러 안에서는 쓸 수 없습니다.
// 위반: 조건에 따라 훅 호출 여부가 달라집니다
function OrderPanel({ orderId }) {
if (!orderId) return null;
const [order, setOrder] = useState(null); // 오류
}
// 해결: 훅을 먼저 부르고 조건은 그 뒤에 둡니다
function OrderPanel({ orderId }) {
const [order, setOrder] = useState(null);
if (!orderId) return null;
return <div>{order?.name}</div>;
}린트 플러그인이 이 규칙을 검사합니다. 경고를 끄지 말고 구조를 바꾸는 방향으로 해결합니다.
커스텀 훅은 그냥 훅을 쓰는 함수입니다
특별한 문법이 있는 것이 아닙니다. 이름이 use로 시작하고 내부에서 훅을 사용하는 함수면 커스텀 훅입니다.
중요한 점은 상태 자체가 공유되지는 않는다는 것입니다. 같은 훅을 두 컴포넌트에서 쓰면 각각 독립된 상태를 갖습니다. 공유되는 것은 로직이지 값이 아닙니다.
// 입력값과 검증을 묶은 훅
function useInput(initialValue, validate) {
const [value, setValue] = useState(initialValue);
const [error, setError] = useState("");
const onChange = (event) => {
const next = event.target.value;
setValue(next);
if (error) {
setError(validate(next) ?? "");
}
};
const onBlur = () => setError(validate(value) ?? "");
return { value, error, onChange, onBlur, reset: () => setValue(initialValue) };
}
// 사용하는 쪽
function OrderForm() {
const receiver = useInput("", validateReceiver);
const phone = useInput("", validatePhone);
return (
<form>
<input value={receiver.value} onChange={receiver.onChange} onBlur={receiver.onBlur} />
{receiver.error && <p className="error">{receiver.error}</p>}
</form>
);
}receiver와 phone은 같은 훅을 쓰지만 서로 다른 상태를 갖습니다. 훅은 로직의 재사용이지 상태의 공유가 아닙니다.
언제 훅으로 분리할지
- 같은 상태와 이펙트 조합이 세 곳 이상에서 반복될 때
- 컴포넌트에서 화면과 무관한 로직이 절반을 넘을 때
- 외부 시스템 연결 코드를 화면에서 떼어내고 싶을 때
- 그 로직만 따로 테스트하고 싶을 때
- 한 곳에서만 쓰는 짧은 로직
- 훅으로 만들면 오히려 흐름을 따라가기 어려운 경우
- 단순히 코드 줄 수를 줄이려는 목적
상태와 이펙트를 쓰지 않는 순수 계산은 그냥 일반 함수로 만듭니다. use 접두사를 붙일 이유가 없습니다.
과한 추상화가 더 나쁠 때가 많습니다
커스텀 훅을 배우면 모든 것을 훅으로 만들고 싶어집니다. 그런데 추상화는 공짜가 아닙니다. 한 번 감싸면 그만큼 흐름을 따라가기 위해 파일을 더 열어야 합니다.
여러 화면에서 조금씩 다르게 쓰려고 옵션을 추가하다 보면, 결국 아무도 이해 못 하는 훅이 됩니다. 그럴 때는 두 개로 나누는 편이 낫습니다.
훅이 다른 훅을 부르고 그 훅이 또 다른 훅을 부르면 어디서 상태가 바뀌는지 추적이 어려워집니다. 깊이는 얕게 유지합니다.
useOrderStuff처럼 모호한 이름이 나오면 책임이 불분명하다는 뜻입니다. 이름이 잘 안 지어지면 분리 기준이 잘못된 경우가 많습니다.
비슷해 보이지만 앞으로 다르게 변할 코드라면, 억지로 합치는 것보다 두는 편이 낫습니다.
먼저 컴포넌트 안에 직접 쓰고, 같은 패턴이 반복되는 것이 확인된 뒤에 훅으로 뽑습니다. 재사용을 미리 예상해서 만든 추상화는 대부분 맞지 않고, 나중에 되돌리기도 어렵습니다.
버전별 참고
본문은 React 19 기준입니다. 18과 달라지는 부분은 아래에 정리합니다.
- 훅 규칙은 React 16.8 이후 모든 버전에서 동일합니다.
- React 19에는 use라는 API가 추가됐습니다. 이름은 비슷하지만 커스텀 훅과 다른 것으로, 조건문 안에서도 호출할 수 있는 예외적인 API입니다.
- React Compiler를 쓰면 훅 규칙 위반이 빌드 단계에서 더 잘 드러납니다. Part 6과 함께 보면 도움이 됩니다.