왜 리액트는 Hook을 순서 기반으로 설계했는가?
먼저: React는 “트리 + 재렌더링” 구조
React는 컴포넌트를 일종의 함수(=렌더링 함수) 로 보고,
"함수를 다시 실행해서 UI를 만들고 diffing하는 방식"으로 동작해.
즉:
function Component(props) {
// 여기서 계산된 결과가 렌더링
}
컴포넌트가 재렌더링되면
전체 함수가 다시 실행됨.
그러면 문제가 생김:
"상태(state)를 어디에 저장하지?"
"함수를 다시 실행했으니까 내부 변수는 전부 초기화되는데?"
그래서 React는 다음 아이디어를 사용함:
컴포넌트를 렌더링할 때 ‘그 컴포넌트 인스턴스 전용 저장공간’을 따로 만든다.
(이걸 React Fiber 노드라고 해)
여기에 Hook 데이터들이 순서대로 저장됨.
핵심: React는 Hook을 “순서 기반 배열”처럼 저장한다
React는 각 컴포넌트 렌더링마다 Hook을 아래처럼 관리함:
0: useState #1 값
1: useState #2 값
2: useEffect #1 값
3: useMemo #1 값
...
즉 "훅을 호출한 순서 = 내부 저장 배열의 인덱스" 인 셈.
이게 되는 이유는 React가 훅을 이렇게 생각:
- useState → 상태 하나 저장
- useEffect → effect 하나 저장
- useMemo → 메모된 값 하나 저장
이걸 어떤 이름이나 식별자로 저장하는 것이 아니라
그냥 “몇 번째 Hook인지”로 구분함.
왜 이렇게 단순한 구조를 선택했을까?
1. 리렌더링이 매우 빠르고 단순해지기 때문
React는 컴포넌트가 재렌더링되면 다시 실행되는데,
Hook을 “순서대로 저장”하면 다음처럼 처리됨:
렌더 시작 → Hook index = 0
useState 호출 → stored[0] 반환
useState 호출 → stored[1] 반환
useEffect 호출 → stored[2] 반환
...
렌더 끝
순서 유지 = 매우 빠르고 단순.
만약 Hook에 ID를 붙이려 했다면?
- 각 Hook마다 고유한 키 필요
- 렌더링마다 ID 매칭해야 함
- DOM diff처럼 복잡한 알고리즘 필요
- 성능 뚝 ↓
React 팀은 이걸 거부했음.
“함수형 컴포넌트는 빠르고 단순해야 한다.”
그래서 Hook은 “순서 = identity” 모델로 고정됨.
2. 함수형 컴포넌트가 “상태없는 함수”처럼 보이도록 하기 위해
원래 React 컴포넌트는 이렇게 단순했음:
function Component() {
return <div />;
}
여기에 상태를 추가해야 하는데 클래스처럼 복잡해지면 안 됨.
그래서 React 팀은 생각했지:
“상태를 컴포넌트 밖에서 관리하고,
컴포넌트는 그 상태를 순서대로 사용할 수만 있으면 되지 않을까?”
그래서 나온 구조:
- 상태 저장은 React가 관리한다
- 컴포넌트는 단지 “첫 번째 상태”, “두 번째 상태”를 읽을 뿐
너무 단순해서 라이프사이클이나 클래스보다 훨씬 쉽고 예측 가능.
3. 훅 호출 규칙을 강제하여 “예측 가능한 UI” 보장
React는 매 렌더링마다 Hook 호출 순서가 같다는 걸 보장해야
- 이전 상태 매칭
- 이전 effect cleanup
- memo 값 재사용
- callback identity 일치
같은 걸 전부 안정적으로 수행할 수 있음.
여기서 조금만 순서가 바뀌면?
useState #1 → 갑자기 effect 저장 칸을 참조함
useMemo #1 → 이전 state 값을 참조함
완전히 망가져버림.
그래서 React는 규칙을 강제함:
- ❌ 조건문 안 hook
- ❌ 반복문 안 hook
- ❌ 일반 함수 안 hook
이게 전부 “순서가 달라질 수 있는 상황”이기 때문.
4. Fiber 구조가 Hook을 순서 기반으로 저장하도록 설계됨
React의 Fiber 노드는 이렇게 생김:
Fiber {
memoizedState: Hook Linked List,
updateQueue: ...
}
React는 훅을 연결 리스트처럼 저장함:
Hook #1 → Hook #2 → Hook #3 → ...
렌더링 때마다 React는 훅을 하나씩 따라가면서:
- useState면 상태 읽기
- useEffect면 effect 저장
- useMemo면 캐싱 확인
이걸 한다.
즉 디테일한 구조는:
- 각 Hook은 “다음 Hook”을 가리키는 링크드 리스트 구조
- React는 렌더링할 때마다 첫 번째 Hook부터 순서대로 traverse
그래서 순서가 어긋나면 바로 터짐.
React가 Hook을 순서 기반으로 만든 이유는:
- 렌더링이 빠르고 단순해지기 때문에
(ID 매칭 같은 복잡한 로직 필요 없음) - 함수형 컴포넌트를 간단하게 유지하기 위해서
- Hook 상태/이펙트/메모 값이 정확하게 대응되기 위해서
- Fiber 구조가 Hook을 링크드 리스트로 “순서대로” 저장하기 때문
- 조건문/반복문이 Hook 순서를 바꾸면 React 전체 흐름이 깨지기 때문