아이디어는 명확했지만, 구현은 또 다른 차원의 도전이었다. ‘훅의 규칙’을 검사하는 ESLint 플러그인을 만드는 것은, 리액트 코어 개발과는 전혀 다른 기술 스택과 전문성을 요구했다.
ESLint 플러그인은 코드를 단순한 텍스트가 아닌, 의미를 가진 구조적인 트리, 즉 ‘추상 구문 트리(AST, Abstract Syntax Tree)’로 변환하여 분석한다. 개발팀은 이 AST를 순회하며 훅의 사용 패턴을 정밀하게 추적해야 했다.
플러그인이 구현해야 할 핵심 로직은 두 가지였다.
첫 번째, ‘훅의 규칙’ 위반 검사:
플러그인은 코드에서 use
로 시작하는 함수 호출을 모두 찾아내야 했다. 그리고 그 호출이 if
문, for
문, 혹은 다른 함수 블록 안에 있는지 검사해야 했다. 만약 그렇다면, 즉시 에러를 발생시켜야 했다.
두 번째, useEffect
의존성 배열 검사:
이것은 훨씬 더 교묘하고 복잡한 문제였다. useEffect
의 두 번째 인자인 의존성 배열은 훅의 성능과 정확성에 지대한 영향을 미쳤다. 만약 개발자가 필요한 의존성을 빠뜨린다면, 훅은 갱신되어야 할 때 갱신되지 않는 ‘stale state’ 버그를 일으킬 것이다. 반대로 불필요한 의존성을 넣는다면, 성능 저하를 유발할 수 있었다.
“개발자가 매번 올바른 의존성 배열을 수동으로 작성하는 것은 거의 불가능에 가깝습니다. 실수가 나올 수밖에 없어요.”
세바스티안이 지적했다.
플러그인은 이 문제까지 해결해야만 했다.
useEffect
함수 본문 안에서 사용된 모든 변수와 함수(props, state 등)를 추적해야 했다. 그리고 그 목록을 의존성 배열에 선언된 값들과 비교해야 했다. 만약 빠진 의존성이 있다면, 플러그인은 경고를 띄우며 어떤 값을 추가해야 하는지 친절하게 알려주어야 했다.
function Greeting({ name }) {
useEffect(() => {
// 이 효과 함수는 'name' prop에 의존한다.
document.title = `Hello, ${name}`;
}, []); // ERROR: 하지만 의존성 배열에는 'name'이 빠져있다!
}
// ESLint 플러그인의 경고:
// "React Hook useEffect has a missing dependency: 'name'.
// Either include it or remove the dependency array."
이 기능은 혁신적이었다.
플러그인은 단순히 규칙 위반을 감시하는 것을 넘어, 개발자의 실수를 미리 예측하고 올바른 코드를 제안하는 적극적인 ‘조력자’의 역할을 하게 될 터였다.
개발은 순탄치 않았다. 수많은 엣지 케이스가 존재했다. 복잡한 클로저, 콜백 함수, 다른 파일에서 가져온 변수 등, 모든 경우를 정확하게 추적하는 것은 매우 어려운 일이었다.
댄과 몇몇 엔지니어들은 몇 주간 이 플러그인 개발에 매달렸다. 그들은 수천 개의 실제 페이스북 코드베이스를 대상으로 플러그인을 테스트하며, 정확도를 높여나갔다.
마침내, eslint-plugin-react-hooks
라는 이름의 파수꾼이 완성되었다.
그것은 눈에 보이지 않았지만, 훅 생태계의 안정성을 지탱하는 가장 중요한 기둥 중 하나가 될 운명이었다.
이 플러그인을 통해, 리액트 팀은 개발자들에게 이렇게 말할 수 있게 되었다.
“훅의 규칙을 외우려고 애쓰지 마세요. 그냥 플러그인을 설치하세요. 그러면 우리가 여러분의 코드를 안전하게 지켜드리겠습니다.”
기술적 난관을 기술로만 풀려는 대신, 인간의 실수를 인정하고 그것을 보완할 도구를 함께 제공하는 것.
이것이 바로 리액트 팀이 추구하는, 개발자 경험을 최우선으로 생각하는 철학이었다.
이제 훅의 기본 골격과 그것을 지탱할 안전장치까지 모두 마련되었다.
팀은 시선을 돌려, 더 복잡한 시나리오를 처리하기 위한 몇 가지 추가적인 훅을 설계하기 시작했다. 그들의 첫 번째 목표는, 댄에게는 너무나도 익숙한 패턴을 훅의 세계로 가져오는 것이었다.