[React] Hook 규칙 & Custom Hook

전체내용 : Hook의 규칙

1. Hook의 규칙

Hook은 JavaScript함수이다. 하지만 Hook을 사용할 때는 두 가지 규칙을 준수해야 한다.
React에서는 규칙을 자동으로 강제하기 위해 linter 플러그인 을 제공한다.


1) 규칙

(1) 최상위(at the Top Level)에서만 Hook을 호출해야 한다.

반복문, 조건문 혹은 중첩된 함수 내에서 Hook을 호출하면 안 된다. 항상 React함수의 최상위에서 Hook을 호출해야 한다.
→ 컴포넌트가 렌더링 될 때마다 동일한 순서로 Hook이 호출되는 것이 보장된다.
→ React가 useStateuseEffect가 여러 번 호출되는 중에도 Hook의 상태를 올바르게 유지할 있게 해준다.


(2) 오직 React 함수 내에서 Hook을 호출해야 한다.

일반적인 JavaScript 함수에서 Hook을 호출하면 안 된다.
가능한 호출 방법은 React함수 컴포넌트에서 호출, Custom Hook에서 호출 두 가지이다.
→ 이 규칙을 지키면 컴포넌트의 모든 상태 관련 로직을 소스코드에서 명확하게 보이도록 할 수 있다.


2) 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", // Checks rules of Hooks
    "react-hooks/exhaustive-deps": "warn" // Checks effect dependencies
  }
}

3) 규칙 설명

React가 특정 state가 어떤 useState호출에 해당하는지 알 수 있는 이유는 React가 Hook이 호출되는 순서에 의존하기 때문이다. 모든 렌더링에서 Hook의 호출 순서는 같기 때문에 올바르게 동작할 수 있다.

하지만, Hook을 조건문 안에서 호출한다면 (첫 번째 규칙을 깸) Hook을 건너뛰는 경우가 생길 수 있고 Hook을 호출하는 순서가 달라지게 된다. 이에 따라 건너뛴 Hook 다음에 호출되는 Hook이 순서가 하나씩 밀리면서 버그를 발생시킨다.
⇒ 컴포넌트 최상위에서만 Hook을 호출하는 이유

만약 조건부로 effect를 실행하길 원한다면, 조건문을 Hook 내부에 넣으면 된다.

useEffect(function persistForm() {
    // 👍 더 이상 첫 번째 규칙을 어기지 않습니다
    if (name !== '') {
      localStorage.setItem('formData', name);
    }
  });


2. 자신만의 Hook 만들기

자신만의 Hook을 만들면 컴포넌트 로직을 함수로 뽑아내어 재사용 할 수 있다.

1) 상태 관련 로직을 컴포넌트에서 공유하는 방법

여기서 예시의 목표는 FriendSatusFriendListItem 컴포넌트에 중복되어있는 로직을 제거하는 것


(1) 사용자 정의 Hook 추출하기

두 개의 자바스크립트 함수에서 같은 로직을 공유하고자 할 때는 또 다른 함수로 분리한다. Hook또한 함수이므로 보통의 함수와 같은 방법으로 사용한다.

사용자 정의 Hook은 이름이 use로 시작하는 자바스크립트 함수다. 사용자 Hook은 다른 Hook을 호출할 수 있다. (이름은 반드시 use로 시작해야한다. Hook규칙이 적용되는지 파악하기 위함)

function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  // ...

  return isOnline; // 온라인 상태 여부 반환
}

(2) 사용자 정의 Hook 이용하기

function FriendStatus(props) {
	const isOnline = useFriendStatus(props.friend.id);
	...
}

function FriendListItem(props) {
	const isOnline = useFriendStatus(props.friend.id);
	...
}
  • 사용자 정의 Hook은 React의 특별한 기능이라기 보다는 기본적으로 Hook의 디자인을 따르는 관습이다.
  • 사용자 정의 Hook의 이름은 use로 시작되어야 한다. 이를 따르지 않으면 특정한 함수가 그 안에서 Hook을 호출하는지 알 수 없기 때문에 Hook 규칙 위반 여부를 자동으로 체크할 수 없다.
  • 같은 Hook을 사용하는 두 개의 컴포넌트는 state를 공유하지 않는다. 사용자 정의 Hook은 상태 관련 로직을 재사용하는 매커니즘일 뿐, Hook을 사용할 때마다 그 안의 stateeffect는 완전히 독립적이다.
  • 각각의 Hook에 대한 호출은 서로 독립된 state를 받는다. useFriendStatus를 직접적으로 호출하기 때문에 React의 관점에서 컴포넌트에서 useStateuseEffect를 호출한 것과 다름없다. 또한, 하나의 컴포넌트 안에서 useStateuseEffect를 여러 번 호출할 수 있고 이들은 모두 완전히 독립적이다.

2) 기타

(1) Hook에서 Hook으로 정보 전달하기

Hook은 함수이기 때문에 Hook사이에서도 정보를 전달할 수 있다.

// 현재 선택된 친구의 ID를 recipientID state 변수에 저장하고
// 사용자가 <select> 선택기에 있는 다른 친구를 선택하면 이를 업데이트하는 코드
// -> 지금 선택되어있는 친구의 온라인 상태여부를 알 수 있다.
const [recipientID, setRecipientID] = useState(1);
const isRecipientOnline = useFriendStatus(recipientID);

return (
    <>
      <Circle color={isRecipientOnline ? 'green' : 'red'} />
      <select
        value={recipientID}
        onChange={e => setRecipientID(Number(e.target.value))}
      >
		...

useState Hook호출은 recipientID state 변수의 최신값을 돌려주기 때문에 이를 useFriendStatus Hook에 인수로 보낼 수 있다.

© 2020 euzl. from JunhoBaik's, Built with Gatsby