- Hook는 자바스크립트 함수이다.
- Hook를 사용할 때 두 가지 규칙을 준수해야 한다.
- 규칙들을 강제하기 위해 linter 플러그인을 제공하고 있다.
최상위(at the Top Level) 에서만 Hook를 호출해야 한다
- 반복문, 조건문, 중첩된 함수 내에서 Hook를 호출하면 안된다.
- 리액트 함수의 최상위 (at the top level) 에서 Hook를 호출해야 한다.
- 컴포넌트가 render 될 때마다 동일한 순서로 Hook가 호출되는 것을 보장한다. ( 여러번 호출되도 Hook의 상태를 올바르게 유지할 수 있게 해준다)
ESLint 플러그인
- eslint-plugin-react-hooks 라는 ESLint를 플러그인을 프로젝트에 추가할 수 있다.
npm install eslint-plugin-react-hooks --save-dev
// ESLint 설정 파일
{
"plugins": [
// ...
"react-hooks"
],
"rules": {
// ...
"react-hooks/rules-of-hooks": "error", // Checks rules of Hooks
"react-hooks/exhaustive-deps": "warn" // Checks effect dependencies
}
}
규칙을 지키는것이 왜 중요한가?
- 컴포넌트에서 State나 Effect Hook를 여러개 사용할 수 있다.
- React는가 Hook의 호출되는 순서에 의존한다
// ------------
// 첫 번째 렌더링
// ------------
useState('Mary') // 1. 'Mary'라는 name state 변수를 선언합니다.
useEffect(persistForm) // 2. 폼 데이터를 저장하기 위한 effect를 추가합니다.
useState('Poppins') // 3. 'Poppins'라는 surname state 변수를 선언합니다.
useEffect(updateTitle) // 4. 제목을 업데이트하기 위한 effect를 추가합니다.
// -------------
// 두 번째 렌더링
// -------------
useState('Mary') // 1. name state 변수를 읽습니다.(인자는 무시됩니다)
useEffect(persistForm) // 2. 폼 데이터를 저장하기 위한 effect가 대체됩니다.
useState('Poppins') // 3. surname state 변수를 읽습니다.(인자는 무시됩니다)
useEffect(updateTitle) // 4. 제목을 업데이트하기 위한 effect가 대체됩니다.
// ...
- Hook의 호출 순서가 렌더링 간에 동일하다면 React는 지역적인 state를 각 Hook에 연동시킨다.
// 🔴 조건문에 Hook을 사용함으로써 첫 번째 규칙을 깼습니다
if (name !== '') {
useEffect(function persistForm() {
localStorage.setItem('formData', name);
});
}
- 위 처럼 Hook를 조건문 안에서 호출한다면 name ! == ' ' 조건은 첫 번째 렌더링에서 true이기 때문에 hook가 동작하지만 다음 렌더링에서 폼을 초기화하면 조건이 false가 되어 hook를 건너뛰기 때문에 Hook 호출순서가 달라진다.
useState('Mary') // 1. name state 변수를 읽습니다. (인자는 무시됩니다)
// useEffect(persistForm) // Hook를 건너뛰었습니다!
useState('Poppins') // 2. (3이었던). surname state 변수를 읽는 데 실패했습니다.
useEffect(updateTitle) // 3. (4였던). 제목을 업데이트하기 위한 effect가 대체되는 데 실패했습니다.
- 이 때문에 Hook는 컴포넌트 최상위 (at the top level) 에서 호출되어야 한다.
useEffect(function persistForm() {
// 컴포넌트 최상위에서 호출해주면서 더 이상 첫 번째 규칙을 어기지 않게 됬습니다.
if (name !== '') {
localStorage.setItem('formData', name);
}
});
댓글