Hooks는 자바스크립트 함수지만 두 가지 규칙을 지키며 사용해야 합니다. 이런 규칙은 linter plugin을 통해 자동으로 적용할 수 있습니다.
Hooks를 루프 내부, 조건(condition), 또는 중첩 함수 내부에서 호출하면 안됩니다. Hooks는 언제나 리액트 함수의 top-level에서 호출되어야 합니다. 이 규칙을 지키면 컴포넌트가 렌더링 될 때 마다 hooks가 동일한 순서로 호출되는 것을 보장할 수 있습니다. 그리고 이런 방식은 리액트가 정확하게 여러 hooks의 상태를 보존할 수 있도록 도와줍니다(useState, useEffect 등).
Hooks를 일반 자바스크립트 함수에서 호출하면 안됩니다. 대신
이 규칙을 따르면 컴포넌트 내부의 stateful logic이 소스코드에서 명확하게 보일 수 있도록 보장합니다.
리액트팀은 위 두 규칙을 강제해주는 eslint-plugin-react-hooks를 제공합니다. 아래와 같은 방식으로 프로젝트에 적용할 수 있습니다.
npm install eslint-plugin-react-hooks --save-dev
// ESLint configuration
{
"plugins": [
// ...
"react-hooks"
],
"rules": {
// ...
"react-hooks/rules-of-hooks": "error", // Checks rules of Hooks
"react-hooks/exhaustive-deps": "warn" // Checks effect dependencies
}
}
위에서 본 것 처럼 우리는 하나의 컴포넌트에서 여러 상태와 이팩트를 사용할 수 있습니다.
function Form() {
// 1. Use the name state variable
const [name, setName] = useState('Mary');
// 2. Use an effect for persisting the form
useEffect(function persistForm() {
localStorage.setItem('formData', name);
});
// 3. Use the surname state variable
const [surname, setSurname] = useState('Poppins');
// 4. Use an effect for updating the title
useEffect(function updateTitle() {
document.title = name + ' ' + surname;
});
// ...
}
리액트는 어떻게 특정 상태가 해당하는 특정 useState call을 구분할 수 있을까요? 리액트는 Hooks가 call되는 순서에 의존합니다.
// ------------
// First render
// ------------
useState('Mary') // 1. Initialize the name state variable with 'Mary'
useEffect(persistForm) // 2. Add an effect for persisting the form
useState('Poppins') // 3. Initialize the surname state variable with 'Poppins'
useEffect(updateTitle) // 4. Add an effect for updating the title
// -------------
// Second render
// -------------
useState('Mary') // 1. Read the name state variable (argument is ignored)
useEffect(persistForm) // 2. Replace the effect for persisting the form
useState('Poppins') // 3. Read the surname state variable (argument is ignored)
useEffect(updateTitle) // 4. Replace the effect for updating the title
// ...
각 렌더마다 Hooks의 call순서가 동일하다면 리액트는 특정 local state를 연관시킬 수 있습니다. 하지만 만약 호출 순서가 달라진다면 어떻게 될까요?
// 🔴 We're breaking the first rule by using a Hook in a condition
if (name !== '') {
useEffect(function persistForm() {
localStorage.setItem('formData', name);
});
}
위의 특정 조건이 다음 렌더링에서 더 이상 참이 아니게 되었다고 가정합시다.
이제 hooks가 불리는 순서가 달라지게 됩니다.
useState('Mary') // 1. Read the name state variable (argument is ignored)
// useEffect(persistForm) // 🔴 This Hook was skipped!
useState('Poppins') // 🔴 2 (but was 3). Fail to read the surname state variable
useEffect(updateTitle) // 🔴 3 (but was 4). Fail to replace the effect
리액트는 두 번째 useState call에서 무엇을 리턴해야 하는지 알 수 없게 됩니다. 리액트는 두 번째 call이 persistForm에 해당하는 effect call이라고 생각하고 있기 때문입니다. 따라서 특정 hooks가 스킵되면 버그가 발생하게 됩니다.
이것이 우리가 Hooks를 항상 컴포넌트의 top-level에서 호출해야 하는 이유입니다. 만약 조건에 따른 동작을 원한다면 Hook 내부에서 해결할 수 있습니다.
useEffect(function persistForm() {
// 👍 We're not breaking the first rule anymore
if (name !== '') {
localStorage.setItem('formData', name);
}
});
출처