React 공식 문서 해석하며 공부하기 : Rules of Hooks

배지로·2021년 9월 16일
0
post-thumbnail

해석하며 공부하는 것을 목적으로 하기 때문에 다수의 의역, 오역이 있음을 미리 밝힙니다.
원본 : https://reactjs.org/docs/hooks-rules.html

Hooks는 리액트 16.8 버전에 새롭게 추가되었습니다. 이로 인해 클래스를 사용하지 않고도 state나 다른 리액트 기능들을 사용할 수 있게 되었죠.

Hooks는 자바스크립트 함수지만 사용하는데 두가지 규칙을 따라야합니다. 이 규칙들을 자동적으로 실행되게 하기 위해 linter 플러그인을 제공합니다:

최상위 계층에서만 Hooks를 호출할 수 있다

반복문,조건문 혹은 중첩된 함수 안에서 Hooks를 호출하지마세요. 언제나 어떤 반환이 이루어지기 전인 리액트 함수 최상위 계층에서 사용해주세요. 이 규칙을 따름으로서, Hooks는 컴포넌트가 렌더링 될 때마다 같은 순서로 호출됨을 보장받을 수 있습니다. 이것은 리액트가 useStateuseEffect호출이 여러번 실행됨에도 Hooks의 state가 정확하게 보존되게 합니다.(더 자세한 부분은 아래에서 다뤄볼게요)

리액트 함수에서만 Hooks를 호출할 수 있다

정규 자바스크립트 함수에서는 Hooks를 호출할 수 없어요. 대신에 할 수 있는 것은:

  • ✅ 리액트 함수 컴포넌트에서 Hooks를 호출하기
  • ✅ 커스터마이징된 Hooks에서 Hooks호출하기(이것에 대해서는 다음 페이지에서 공부해볼 거에요)

이 규칙을 따름으로서, 컴포넌트의 모든 state 관련 로직은 소스코드에서 명확하게 보이도록 할 수 있습니다.


ESLint 플러그인

위의 두가지 규칙을 실행하도록하는 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관련 로직을 재사용할 수 있게 합니다.

profile
웹 프론트엔드 새싹🌱

0개의 댓글