안녕하세요. IT 엘도라도 에 오신 것을 환영합니다.
글을 쓰는 것은 귀찮지만 다시 찾아보는 것은 더 귀찮습니다.
완전한 나만의 것으로 만들기 위해 지식을 차곡차곡 저장해 보아요.   포스팅 둘러보기 ▼

리액트 (React)

[React] 공식 문서 요약 - 나만의 Hook 만들기

피그브라더 2020. 9. 12. 17:07
 

Building Your Own Hooks – React

A JavaScript library for building user interfaces

reactjs.org

* React 공식 문서를 꼼꼼히 읽으면서 개인적으로 요약한 내용입니다. 잘못된 내용이 있다면 지적 부탁드립니다.

 

1. 개요 - 커스텀 Hook이 필요한 경우

기존의 React에서는 컴포넌트 내에 존재하는 상태 관련 로직을 다른 컴포넌트에서도 재사용하려면 고차 컴포넌트나 Render Props와 같은 패턴을 사용해야 했다. 그러나 이러한 패턴들은 컴포넌트 트리에 부가적인 컴포넌트를 추가시킴으로써 래퍼 지옥(Wrapper Hell)과 같은 현상을 유발했다. React 16.8.0부터 도입된 Hook을 이용하면 이러한 문제없이 컴포넌트의 상태 관련 로직을 쉽게 공유하고 재사용하는 것이 가능하다. 바로 커스텀 Hook을 만드는 것이다.

 

다음 예시를 보자. FriendStatus 컴포넌트는 특정 ID에 해당하는 친구의 접속 상태를 구독하는 로직을 담고 있다.

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

function FriendStatus(props) {
  const [isOnline, setIsOnline] = useState(null);
  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}

 

이제 해당 로직을 다른 컴포넌트에서도 재사용하고 싶다고 가정하자. 즉, 다른 컴포넌트에서도 특정 ID에 해당하는 친구의 접속 상태를 구독해야 할 필요가 있다고 생각해보는 것이다. 이때 가장 먼저 떠올릴 수 있는 방법은 해당 로직의 코드를 그대로 복사해서 붙여 넣는 것이다. 이게 너무 비효율적이라면, 앞서 말한 대로 고차 컴포넌트나 Render Props 패턴을 이용할 수도 있다. 하지만 이 방법 또한 컴포넌트 트리를 복잡하게 만들기 때문에 만족스럽지는 못하다. 이때가 바로 커스텀 Hook을 만들어서 사용할 때이다. 그러면 이제 어떻게 커스텀 Hook을 만들고 이를 통해 로직을 재사용할 수 있는지 차근차근 알아보도록 하자.

 

2. 커스텀 Hook 추출하기

커스텀 Hook은 이름이 use로 시작하는 JavaScript 함수이다. 커스텀 Hook은 또 다른 Hook을 호출할 수 있다. 커스텀 Hook은 재사용하고자 하는 컴포넌트 내 상태 관련 로직을 함수로 추출함으로써 만들 수 있다. 위 예시에서 FriendStatus 컴포넌트가 가지고 있는 구독 로직을 커스텀 Hook으로 만들면 다음과 같다. useFriendStatus() 함수가 바로 커스텀 Hook이다.

import { useState, useEffect } from 'react';

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

  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

    ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
    };
  });

  return isOnline;
}

 

특별히 새로운 것은 없다. 기존의 로직을 그대로 함수로 추출했을 뿐이다. 다만 커스텀 Hook에서도 컴포넌트에서와 마찬가지로 반드시 최상위에서 Hook을 호출해야 한다. 커스텀 Hook이 지켜야 하는 특정한 시그니처는 없다. 무엇을 인자로 전달받고 무엇을 반환할지는 커스텀 Hook을 만드는 사람의 마음이다. 관습에 따라 이름이 use로 시작할 뿐, 일반적인 JavaScript 함수와 크게 다르지 않은 것이다. 위 예시에서 useFriendStatus() 함수의 경우 친구의 접속 상태를 구독하는 것이 목표이기 때문에, 친구의 ID를 인자로 전달받고 해당 친구의 접속 상태를 반환하도록 하였다.

 

3. 커스텀 Hook 이용하기

이제 우리가 만든 커스텀 Hook을 컴포넌트에서 불러와 사용해보자. 맨 처음 예시에서 보여주었던 FriendStatus 컴포넌트는 다음과 같이 커스텀 Hook을 불러와 사용할 수 있다. 이는 기존의 코드와 완전히 동일한 방식으로 동작한다. 다만 차이점은 이제 해당 로직을 다른 컴포넌트에서도 아주 간단한 방법으로 불러와 재사용할 수 있게 되었다는 점이다. 잘 살펴보면 알겠지만, 결국 커스텀 Hook은 특별한 기능이라기보다 단순히 Hook의 디자인을 따르는 일종의 관습이자 패턴이라는 것을 알 수 있다. 따라서 학습하는 데 큰 부담이 되지 않는다.

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

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}

 

커스텀 Hook의 이름은 반드시 use로 시작해야 할까? 관습일 뿐이지만, 사실상 강제되는 규칙이라고 봐도 무방하다. 이 규칙을 따르지 않으면 특정 함수가 그 안에서 Hook을 호출할지 말지 여부를 알아낼 수 없기 때문이다. Hook 규칙의 위반 여부를 검사할 수 없게 된다.

 

꼭 기억해야 하는 것은 각각의 커스텀 Hook 호출은 완전히 독립적이라는 것이다. 즉, 커스텀 Hook은 로직을 공유하는 수단일 뿐이지 해당 Hook에 대응하는 상태까지 공유하는 것은 아니다. 동일한 커스텀 Hook을 여러 번, 혹은 여러 군데에서 호출하면 각각은 독립적인 상태를 만들어 낸다. 이는 한 컴포넌트 내에서 여러 번 useState() 함수 혹은 useEffect() 함수를 호출하는 것과 개념적으로 완전히 동일하다.