커뮤니티의 초기 혼란이 어느 정도 진정되자, 댄은 더 근본적인 문제에 집중했다. 바로 ‘멘탈 모델(Mental Model)’의 전파였다. 개발자들이 훅을 그저 API 사용법만 아는 수준을 넘어, 그 내부 동작 원리를 직관적으로 이해하게 만들어야 했다. 그렇지 않으면 훅은 언제 터질지 모르는 시한폭탄과 같았다.
가장 큰 오해의 소지가 있는 부분은 ‘어떻게 함수가 자신의 상태를 기억하는가?’라는 질문이었다. 많은 개발자들이 이것을 일종의 ‘마법’으로 받아들이고 있었다.
댄은 자신의 블로그에 새로운 글을 쓰기 시작했다. 그의 목표는 이 마법처럼 보이는 현상의 베일을 벗겨내고, 그 안에 숨겨진 지극히 단순하고 기계적인 원리를 설명하는 것이었다.
그는 글의 서두에 이렇게 적었다.
“훅은 마법이 아닙니다. 이 글에서는 훅이 어떻게 작동하는지 그 내부를 함께 탐험해 보겠습니다.”
그는 복잡한 리액트 코어 로직을 직접 보여주는 대신, 아주 단순화된 비유와 가상 코드를 사용했다.
“리액트가 컴포넌트별로 상태를 저장하는 비밀스러운 배열을 가지고 있다고 상상해 보세요. Counter 컴포넌트가 처음 렌더링될 때, 이 배열은 비어있습니다.”
내부 메모리:
Counter.state = []
“여러분이 코드에서 useState('apple')을 처음 호출하면, 리액트는 이 배열에 ‘apple’을 추가하고, useState('orange')을 두 번째로 호출하면 ‘orange’를 추가합니다.”
내부 메모리:
Counter.state = ['apple', 'orange']
“여기까지는 간단합니다. 진짜 질문은 리렌더링 때 발생하죠. 컴포넌트 함수가 다시 호출될 때, 리액트는 어떻게 어떤 useState가 어떤 상태에 해당하는지 알 수 있을까요?”
댄은 바로 이 지점에서 핵심 원리를 밝혔다.
“정답은, 리액트는 아무것도 모른다는 겁니다. 리액트는 그저 현재 몇 번째 useState 호출인지를 가리키는 인덱스 카운터 하나만 가지고 있을 뿐입니다.”
그는 리렌더링 과정을 단계별로 설명했다.
- 리렌더링이 시작되면, 리액트는 인덱스 카운터를 0으로 초기화한다.
- 첫 번째
useState가 호출된다. 리액트는 “아, 인덱스가 0이군. 그럼 상태 배열의 0번 값을 주면 되겠네”라고 생각하고 ‘apple’을 반환한다. 그리고 인덱스 카운터를 1로 증가시킨다. - 두 번째
useState가 호출된다. 리액트는 “인덱스가 1이군. 그럼 상태 배열의 1번 값을 줘야지”라고 생각하고 ‘orange’를 반환한다. 그리고 인덱스 카운터를 2로 증가시킨다.
이 설명은 훅의 심장에 놓인 비밀, 즉 ‘호출 순서의 불변성’을 명쾌하게 드러냈다. 훅이 작동하는 이유는 리액트가 똑똑해서가 아니라, 개발자가 매 렌더링마다 똑같은 순서로 훅을 호출해주리라는 ‘약속’을 지키기 때문이었다.
이 글이 공개되자, 커뮤니티의 반응은 뜨거웠다.
“이제야 이해가 된다! 마법이 아니라 그냥 배열 인덱스 맞추기였잖아?”
“이 설명을 들으니 왜 if 문 안에서 훅을 쓰면 안 되는지 명확하게 알겠다. 배열 인덱스가 완전히 꼬여버리겠구나.”
댄의 글은 수많은 개발자들의 머릿속에 있던 물음표를 느낌표로 바꾸어 놓았다. 그는 복잡한 기술을 쉽고 공감 가는 언어로 풀어내는, 자신만의 탁월한 재능을 다시 한번 증명해 보였다.
멘탈 모델을 전파하려는 그의 노력은 여기서 그치지 않았다. 그는 콘퍼런스 발표에서 사용했던 은행 창구 비유를 다듬어 공유하고, 훅의 규칙이 왜 필요한지를 반복해서 설명했다.
이러한 끊임없는 소통 덕분에, 훅은 더 이상 ‘블랙박스’가 아니었다. 개발자들은 이제 훅의 내부를 투명하게 들여다볼 수 있게 되었고, 그 원리를 이해함으로써 훅을 더욱 자신감 있고 안전하게 사용할 수 있게 되었다.
첫 번째, 그리고 가장 중요한 멘탈 모델이 성공적으로 커뮤니티에 뿌리내리고 있었다. 그리고 그 뿌리 위에서, 새로운 생태계가 싹을 틔울 준비를 하고 있었다.


