[Hook] Hook의 규칙(1)

JueunPark·2022년 1월 17일
0

React

목록 보기
1/2
post-thumbnail

리액트는 Hook의 순서를 기억한다.

  if (count !== 3) {
    useEffect(() => {
      console.log("if count change, this will be print");
    }, [count]);
  }

위의 코드처럼 useEffect를 if문으로 감싸면 에러가 나는 이유가 무엇일까?
전체 코드를 한번 보자

import React, { useEffect, useState } from "react";

function Test(props) {
  const [count, setCount] = useState(0);
  
  if (count !== 3) {
    useEffect(() => {
      console.log("if count change, this will be print");
    }, [count]);
  }
  
  useEffect(() => {
    console.log("first time rendering");
  }, []);

  return (
    <div>
      <button type="button" onClick={() => setCount(count + 1)}>
        UP
      </button>
      {count}
    </div>
  );
}

export default Test;

UP 버튼을 누르면 숫자가 하나씩 증가하는 코드이다. useEffect에 씌워진 if문을 제거하면 잘 작동한다.

그런데 왜 useEffect를 조건에 따라 다르게 실행하려고 하면 에러가 나는걸까?
그건 Reacthook함수들의 호출 순서를 기억하고 있기 때문이다.

useEffect에 인자로 넣은 콜백함수와 의존성 리스트를 살펴보자.

  useEffect(() => {
      console.log("if count change, this will be print");
    }, [count]);

의존성 리스트로 넣은 [count] 요소 중의 하나가 바뀌면 useEffect가 인자로 받은 콜백함수{ console.log("if... print") } 를 실행시킨다.

즉 useEffect자체가 때에 따라 실행되고 안되는 것이 아니라, useEffect는 항상 실행되고 인자로 받은 콜백함수를 실행시킬 것인지 말지를 useEffect가 판단하는 것이다.

앞서 언급한 것처럼 React는 이런 Hook함수들의 실행 순서를 기억하고 있다. 그래서 Test 컴포넌트가 호출되었을때, 이전에 호출되었던 Hook함수들의 순서도와 다른 부분이 생기면 무언가 잘못되었음을 감지하는 것이다.

Line 11:5:  React Hook "useEffect" is called conditionally. React Hooks must be called in the exact same order in every component render  react-hooks/rules-of-hooks

ES Lint를 사용하면 내 코드에 문제가 있음을 알려준다.
React Hooks must be called in the exact same order in every component render라는 메세지를 통해 순서를 보장하지 않는 코드를 작성했음을 알 수 있다.

rules-of-hooks의 내용을 공식문서에서 좀 더 살펴보자.

반복문, 조건문 혹은 중첩된 함수 내에서 Hook을 호출하지 마세요. 대신 early return이 실행되기 전에 항상 React 함수의 최상위(at the top level)에서 Hook을 호출해야 합니다. 이 규칙을 따르면 컴포넌트가 렌더링 될 때마다 항상 동일한 순서로 Hook이 호출되는 것이 보장됩니다. 이러한 점은 React가 useState 와 useEffect 가 여러 번 호출되는 중에도 Hook의 상태를 올바르게 유지할 수 있도록 해줍니다. 이 점에 대해서 궁금하다면 아래에서 자세히 설명해 드리겠습니다.

공식문서에 나와 있는 자세한 설명에 따르면 내가 쓴 코드는 아래와 같이 렌더링 된다.

  • code
import React, { useEffect, useState } from "react";

function Test(props) {
  const [count, setCount] = useState(0);

  if (count !== 3) {
    useEffect(() => {
      console.log("if count change, this will be print");
    }, [count]);
  }
  
  useEffect(() => {
    console.log("first time rendering");
  }, []);

  return (
    <div>
      <button type="button" onClick={() => setCount(count + 1)}>
        UP
      </button>
      {count}
    </div>
  );
}

export default Test;
  • 첫번째 렌더링
  useState(0)           // 1. 값으로 0을 가지는 count state 변수를 선언
  useEffect()           // 2. console.log("if...")를 실행
  useEffect()           // 3. console.log("first...")를 실행
  • UP 버튼을 클릭합니다.
  • setCount()함수의 실행으로 state 변화가 감지되어 컴포넌트에 대한 리렌더링이 일어난다.
  • 두번째 렌더링
  useState(1)           // 1. 값으로 1을 가지는 count state 변수를 선언
  useEffect()           // 2. console.log("if...")를 실행
  useEffect()           // 3. console.log("first...")를 실행
  • 첫번째 렌더링과 두번째 렌더링은 순서가 같습니다. 그런데 세번째 렌더링은 어떨까요?
    if (count !== 3) 조건문의 조건이 충족되지 않아 지금까지 실행되던 첫번째 useEffect가 실행되지 않는다.
  useState(1)           // 1. 값으로 1을 가지는 count state 변수를 선언
  //useEffect()         // 🙀 hook을 건너
  useEffect()           // (원래 3이었던) 2. React의 예상과 불일치하는 hook으로 실행에 실패
profile
노트정리

0개의 댓글