[React]React Hook 규칙

LMH·2022년 11월 30일
0
post-thumbnail

오늘은 React Hook을 사용하는 규칙에 대해서 정리하고자 합니다. Hook은 javaScript 함수이며, 이를 사용하기 위해서는 두 가지 규칙을 준수해야합니다.

React에 Hook을 도입한 이유

사용 규칙

1. 최상위에서만 Hook을 호출

반복문, 조건문, 중첩된 함수 내에서 Hook을 호출하면 안 됩니다. 리액트에서는 규칙을 준수할 경우 컴포넌트가 렌더링 될때마다 항상 동일한 순서로 Hook이 호출 되는 것을 보장 하고 있습니다.

2. React 함수 내에서 Hook을 호출

  • React 함수 컴포넌트 내에서 Hook을 호출
  • Custom Hook에서 Hook을 호출

ESLint 플러그인

리액트에서는 이 두 가지 규칙을 강제하는 ESLint 플러그인을 사용하고 있습니다.이 플러그인은 CRA에 기본적으로 포함되어 있습니다.

// ESLint 설정 파일
{
  "plugins": [
    // ...
    "react-hooks"
  ],
  "rules": {
    // ...
    "react-hooks/rules-of-hooks": "error", // Checks rules of Hooks
    "react-hooks/exhaustive-deps": "warn" // Checks effect dependencies
  }
}

예제

위에서 이야기한 규칙이 어떻게 작용하는지 실제 예제를 통해서 알아 봅시다. 컴포넌트 내에서는 여러개의 Hook을 사용할 수 있습니다. 이 경우 React는 각 Hook이 호출되는 순서를 통해 특정 Hook이 호출 되었음을 알 수 있습니다.

이러한 작업이 가능한 이유는 Hook의 호출 순서가 항상 동일 하기 때문입니다.

function Form() {

  const [name, setName] = useState('Mary');

  useEffect(function persistForm() {
    localStorage.setItem('formData', name);
  });

  const [surname, setSurname] = useState('Poppins');

  useEffect(function updateTitle() {
    document.title = name + ' ' + surname;
  });
}

위의 코드를 보면 From 컴포넌트 내부에 useState와 useEffect가 2개씩 선언되어 있습니다.

렌더링의 순서는 다음과 같습니다. 여러번 렌더링 되어서 각 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가 대체됩니다.

if문을 내부에서 useEffect를 사용하여 첫 번째 규칙을 어긴다면 어떻게 될까요?

  if (name !== '') {
    useEffect(function persistForm() {
      localStorage.setItem('formData', name);
    });
  }

첫 번째 렌더링에서 name !== '' 조건은 첫 번째 렌더링에서 true 기 때문에 Hook은 동작합니다. 하지만 사용자가 다음 렌더링에서 조건이 false가 되기 때문에 if문 내부의 useEffect는 호출되지 않습니다.

useState('Mary')           // 1. name state 변수를 읽습니다. 
// useEffect(persistForm)  //  Hook을 건너뛰었습니다!
useState('Poppins')        // 2 surname state 변수를 읽는 데 실패했습니다.
useEffect(updateTitle)     // 3 제목을 업데이트하기 위한 effect가 대체되는 데 실패했습니다.

리액트는 두 번째 useState Hook 호출에 대해 무엇을 반환할지 몰랐습니다. 이전 렌더링 때처럼 컴포넌트 내에서 두 번째 Hook 호출이 persistForm effect와 일치할 것이라 예상했지만 그렇지 않았습니다.

이렇게 Hook의 호출 순서가 밀리게되면서 버그가 발생합니다.

useEffect를 특정한 조건에 따라 분기하여 사용하고 싶다면 useEffect의 콜백함수 내부에 if문을 생성하여 사용이 가능합니다.

  useEffect(function persistForm() {
    if (name !== '') {
      localStorage.setItem('formData', name);
    }
  });

reference : 리액트 공식 홈페이지

profile
새로운 것을 기록하고 복습하는 공간입니다.

0개의 댓글