해석하며 공부하는 것을 목적으로 하기 때문에 다수의 의역, 오역이 있음을 미리 밝힙니다.
원본 : https://reactjs.org/docs/hooks-rules.html
Hooks는 리액트 16.8 버전에 새롭게 추가되었습니다. 이로 인해 클래스를 사용하지 않고도 state나 다른 리액트 기능들을 사용할 수 있게 되었죠.
Hooks는 자바스크립트 함수지만 사용하는데 두가지 규칙을 따라야합니다. 이 규칙들을 자동적으로 실행되게 하기 위해 linter 플러그인을 제공합니다:
반복문,조건문 혹은 중첩된 함수 안에서 Hooks를 호출하지마세요. 언제나 어떤 반환이 이루어지기 전인 리액트 함수 최상위 계층에서 사용해주세요. 이 규칙을 따름으로서, Hooks는 컴포넌트가 렌더링 될 때마다 같은 순서로 호출됨을 보장받을 수 있습니다. 이것은 리액트가 useState
와 useEffect
호출이 여러번 실행됨에도 Hooks의 state가 정확하게 보존되게 합니다.(더 자세한 부분은 아래에서 다뤄볼게요)
정규 자바스크립트 함수에서는 Hooks를 호출할 수 없어요. 대신에 할 수 있는 것은:
이 규칙을 따름으로서, 컴포넌트의 모든 state 관련 로직은 소스코드에서 명확하게 보이도록 할 수 있습니다.
위의 두가지 규칙을 실행하도록하는 eslint-plugin-react-hooks라고 불리는 ESLint 플러그인을 출시했습니다. 이 플러그인을 사용해보고 싶으면 프로젝트에 추가하면 됩니다:
이 플러그인은 기본적으로 Create React App에 포함되어 있습니다.
npm install eslint-plugin-react-hooks --save-dev
//ESLint 설정 파일
{
"plugins":[
//...
"react-hooks"
],
"rules":{
//...
"react-hooks/rules-of-hooks":"error", //Hooks의 규칙 확인
"react-hooks/exhaustive-deps":"warn" //effect 의존성 확인
}
}
지금 당신만의 Hooks를 작성하는 방법에 대해서 설명하는 다음 페이지로 넘어가셔도 됩니다. 이 페이지에서는, 이 규칙들에 대한 근거를 계속해서 설명할 예정입니다.
이전에 배운대로, 우리는 여러개의 State나 Effect Hooks를 하나의 컴포넌트에서 사용하는 것이 가능합니다:
function Form(){
//1. state 변수 name 사용
const [name,setName]=useState('Mary');
//2. 폼을 지속하기 위해서 effect를 사용
useEffect(function persistForm(){
localStorage.setItem('formData',name);
});
//3. state 변수 surname 사용
const [surname,setSurname]=useState('Poppins');
//4. 제목을 업데이트하기 위해서 effect를 사용
useEffect(function updateTitle(){
document.title=name+' '+surname;
});
//...
}
리액트는 어떤 state가 어떤useState
호출과 일치하는지 어떻게 아는 걸까요? 답은 리액트가 Hooks가 호출되는 순서에 의존한다는 점에 있습니다. 예시는 Hook 호출 순서는 렌더링마다 같다는 것을 보여줍니다:
//---------
//첫번째 렌더링
//---------
useState('Mary') //1. 'Mary'로 state 변수 name을 초기화
useEffect(persistForm) //2. 폼을 지속하기 위해 effect 추가
useState('Poppins') //3. 'Poppins'로 state 변수 surname 초기화
useEffect(updateTitle) //4. 제목을 업데이트하기 위해 effect 추가
//---------
//두번째 렌더링
//---------
useState('Mary') //1. state변수 name을 읽어온다(매개변수 무시됨)
useEffect(persistForm) //2. 폼을 지속하기 위해 effect 교체
useState('Poppins') //3. state 변수 surname을 읽어온다(매개변수 무시됨)
useEffect(updateTitle) //4. 제목을 업데이트하기 위해 effect 교체
//...
렌더링할 때 Hooks 호출의 순서가 같은 이상, 리액트는 로컬 state를 각 Hooks에 연결시킬 수 잇습니다. 그러나 조건문에 Hook 호출(예를 들어 persistForm
effect)을 넣으면 어떤 일이 벌어질까요?
//🔴 조건문에서 Hook을 사용함으로써 첫번째 규칙을 어기고 있습니다
if (name!==''){
useEffect(function persistForm(){
localStorage.setItem('formData',name);
});
}
name!==''
반복문 조건은 첫번째 렌더링에서 true
를 만족하기 때문에, 이 Hook을 실행시킬수 있습니다. 하지만, 다음 렌더링에서 false
를 만족하기 때문에 폼을 초기화하게됩니다. 렌더링 과정에서 Hook을 건너뛰게 되면서, Hooks 호출의 순서는 달라집니다:
useState('Mary') //1. state변수 name을 읽어온다(매개변수 무시됨)
//useEffect(persistForm) // 🔴 이 Hook을 건너뜁니다!
useState('Poppins') // 🔴 2(원래는 3) state 변수 surname을 읽어오는 것 실패
useEffect(updateTitle) //🔴 3(원래는 4) effect를 교체하는 것 실패
리액트는 두번째 useState Hook 호출로 무엇을 반환해야하는지 알지 못합니다. 리액트는 이전 렌더링처럼 컴포넌트 내의 두번째 Hook 호출은 persistForm effect와 일치한다고 예상하지만, 더이상 그렇지 않죠. 그 시점부터, 우리가 건너뛴 Hook 호출 다음의 모든 호출들은 하나씩 이동하기 때문에, 버그를 발생시킬 수 있습니다.
이것이 왜 Hooks는 컴포넌트의 최상위 계층에서만 호출되어야 하는 이유입니다. 만약 effect를 조건부로 실행시키길 원한다면, 우리의 Hook 안에 조건문을 넣을 수 있습니다.
useEffect(function persistForm(){
//👍 첫번째 규칙을 깨뜨리지 않습니다!
if(name!==''){
localStorage.setItem('formData',name);
}
});
제공된 lint 규칙을 이용한다면 이 문제에 대해서 걱정할 필요가 없습니다. 그러나 이제 당신은 왜 Hooks가 이런 식으로 동작해야하는지, 이러한 규칙이 방지할 수 있는 문제에 대해서 알았습니다.
마침내, 우리는 나만의 Hooks 작성법에 대해서 배울 준비가 되었습니다! 커스텀 Hooks는 리액트에서 제공한 Hooks를 나만의 추상화된 로직으로 사용할 수 있도록 결합해주고, 다른 컴포넌트 사이에서 공통의 state관련 로직을 재사용할 수 있게 합니다.