[Front-end] 개발자 공부

[개발 공부 81일차] React 공식 문서 | Custom Hook으로 로직 재사용하는 법

MOLLY_ 2024. 7. 12. 07:00
728x90

<목차>

0. TL;DR
1. Custom Hook: 컴포넌트 간 로직을 공유할 때 사용
2. 컴포넌트로부터 Custom Hook 추출하기
3. 작명 규칙
4. Custom Hook은 state 자체를 공유하는 게 아니라 state 저장 로직을 공유함
5. Custom Hook 작명법
6. Custom Hook으로 Effect를 감싸는 것이 종종 유용한 이유

 
 

헐 이게 대체 뭐야?!
아아.. 모르는가?
이건 바로 'Custom Hook’이라고 하는 것이다.
Custom Hook 최고!!!!!

 
 

0. TL;DR

  1. Custom Hook을 사용하면 컴포넌트 간 로직을 공유할 수 있음
  2. Custom Hook의 이름은 use 뒤에 대문자로 시작되어야 함
  3. Custom Hook은 state 자체가 아닌 state 저장 로직만 공유
  4. Custom Hook을 통해 받는 이벤트 핸들러는 Effect로 감싸야 함

 
 

1. Custom Hook: 컴포넌트 간 로직을 공유할 때 사용

바로 어떻게 사용하는지, 그 사용법에 대해 알아보자.
 
네트워크에 크게 의존하는 앱(대부분의 앱이 그렇듯)을 개발 중이라고 생각해 보자. 유저가 앱을 사용하는 동안 네트워크가 갑자기 사라진다면, 유저에게 경고하고 싶을 것이다.
 
이런 경우, 컴포넌트에는 다음의 2가지가 필요할 것이다.

  1. 네트워크가 온라인 상태인지 아닌지 추적하는 하나의 state
  2. 전역 online (온라인)offline (오프라인) 이벤트를 구독하고, 이에 맞춰 state를 업데이트하는 Effect

 
코드로 구현하면 다음과 같다.

import { useState, useEffect } from 'react';

export default function StatusBar() {
  const [isOnline, setIsOnline] = useState(true);
  useEffect(() => {
    function handleOnline() {
      setIsOnline(true);
    }
    function handleOffline() {
      setIsOnline(false);
    }
    window.addEventListener('online', handleOnline);
    window.addEventListener('offline', handleOffline);
    return () => {
      window.removeEventListener('online', handleOnline);
      window.removeEventListener('offline', handleOffline);
    };
  }, []);

  return <h1>{isOnline ? '✅ 온라인' : '❌ 연결 안 됨'}</h1>;
}

 

import { useState, useEffect } from 'react';

export default function SaveButton() {
  const [isOnline, setIsOnline] = useState(true);
  useEffect(() => {
    function handleOnline() {
      setIsOnline(true);
    }
    function handleOffline() {
      setIsOnline(false);
    }
    window.addEventListener('online', handleOnline);
    window.addEventListener('offline', handleOffline);
    return () => {
      window.removeEventListener('online', handleOnline);
      window.removeEventListener('offline', handleOffline);
    };
  }, []);

  function handleSaveClick() {
    console.log('✅ 진행사항 저장됨');
  }

  return (
    <button disabled={!isOnline} onClick={handleSaveClick}>
      {isOnline ? '진행사항 저장' : '재연결 중...'}
    </button>
  );
}

 
위 두 컴포넌트는 잘 동작하지만, 둘 사이의 로직이 중복되는 점은 아쉽다. 둘 사이의 로직을 재사용해 보자.
 
 

2. 컴포넌트로부터 Custom Hook 추출하기

useOnlineStatus Hook을 Custom Hook으로 만들자. 그럼 두 컴포넌트를 단순화할 수 있고, 둘 간의 중복을 제거할 수 있게 된다.

// App.js

import { useOnlineStatus } from './useOnlineStatus.js';

function StatusBar() {
	// [첫 번째 사용] 다른 컴포넌트에서 사용할 땐, 이렇게(useOnlineStatus()) 호출하여 사용
  const isOnline = useOnlineStatus();
  return <h1>{isOnline ? '✅ 온라인' : '❌ 연결 안 됨'}</h1>;
}

function SaveButton() {
	// 두 번째 사용
  const isOnline = useOnlineStatus();

  function handleSaveClick() {
    console.log('✅ 진행사항 저장됨');
  }

  return (
    <button disabled={!isOnline} onClick={handleSaveClick}>
      {isOnline ? '진행사항 저장' : '재연결 중...'}
    </button>
  );
}

export default function App() {
  return (
    <>
      <SaveButton />
      <StatusBar />
    </>
  );
}

 

// useOnlineStatus.js

import { useState, useEffect } from 'react';

export function useOnlineStatus() {
	// 이 아래부터
  const [isOnline, setIsOnline] = useState(true);
  
  useEffect(() => {
    function handleOnline() {
      setIsOnline(true);
    }
    
    function handleOffline() {
      setIsOnline(false);
    }
    
    window.addEventListener('online', handleOnline);
    window.addEventListener('offline', handleOffline);
    
    return () => {
      window.removeEventListener('online', handleOnline);
      window.removeEventListener('offline', handleOffline);
    };
  }, []);
  // 이 위까지가 중복되는 코드임!
  // 쏙 빼다가 Custom Hook으로 만든 것
  
  // 함수의 마지막에 isOnline을 반환하면, 컴포넌트가 그 값을 읽을 수 있게 해줌
  return isOnline;
}

 
이제 컴포넌트는 반복되는 로직이 많지 않게 되었다.
 
중요한 건, 두 컴포넌트의 내부 코드가 어떻게 그것을 하는지(브라우저 이벤트 구독하기)보다 ‘그들이 무엇을 하려는지(온라인 state 사용하기)’에 대해 설명하고 있다는 점이다.
 
Costom Hook을 만들면 불필요한 세부사항을 숨길 수 있다. 컴포넌트의 코드는 목적만을 나타낼 뿐, 실행 방법에 대해선 나타내지 않는다.
 
 

3. 작명 규칙

  1. React 컴포넌트의 이름은 항상 ‘대문자로 시작’해야 한다.
    (예시: StatusBar, SaveButton) 또한 React 컴포넌트는 JSX처럼 어떻게 보이는지 React가 알 수 있는 무언가를 반환해야 한다.
  2. Hook의 이름은 use 뒤에 ‘대문자로 시작’해야 한다.
    (예시: useState (내장된 Hook) or useOnlineStatus (앞서 작성한 커스텀 Hook)) Hook들은 어떤 값이든 반환할 수 있다.

이런 규칙들은 컴포넌트를 볼 때, 어디에 state와 Effect 및 다른 React 기능들이 ‘숨어’ 있는지 알 수 있게 해준다. (예를 들어, 만약 useOnlineStatus() 함수의 경우, 높은 확률로 내부에 다른 Hook을 사용하고 있단 걸 알 수 있다.)
 
 

렌더링 중에 호출되는 모든 함수는 ‘use’ 접두사로 시작해야 할까?

아니다. Hook을 호출하지 않는 함수는 Hook일 필요가 없다.
함수가 어떤 Hook도 호출하지 않는다면, use를 이름 앞에 작성하지 말자. 대신, use 없이 일반적인 함수로 작성하자.
 

// [🚨 안 좋은 예시] Hook을 사용하고 있지 않는 Hook
function useSorted(items) {
  return items.slice().sort();
}

// [✅ 좋은 예시] Hook을 사용하지 않는 일반 함수
function getSorted(items) {
  return items.slice().sort();
}

 
적어도 하나의 Hook을 내부에서 사용한다면 반드시 함수 앞에 use를 작성해야 한다. (그리고 이 자체로 Hook이 된다.)
 
그리고 지금 당장은 함수에서 어떤 Hook도 사용하지 않지만, 미래에 Hook을 호출할 계획이 있다면 use를 앞에 붙여 이름 짓는 것이 가능하다.
 

//[ ✅ 좋은 예시] 추후에 다른 Hook을 사용할 가능성이 있는 Hook
function useAuth() {
  // TODO: 인증이 수행될 때 해당 코드를 useContext(Auth)를 반환하는 코드로 바꾸기
  return TEST_USER;
}

// 위 컴포넌트는 조건에 따라 호출할 수 없게 됨

 
Custom Hook을 사용하는 Hook은 조건에 따라 호출할 수 없다. 이건 실제로 Hook을 내부에 추가해 호출할 때, 매우 중요하다. 지금이든 나중이든 Hook을 내부에서 사용할 계획이 없다면, Hook으로 만들지 말자.
 
 

4. Custom Hook은 state 자체를 공유하는 게 아니라 state 저장 로직을 공유함

위 네트워크 연결 예시에서 Custom Hook으로 코드를 분리하여 중복된 부분을 걷어내기 전에도 동일하게 동작하였다.
 
이를 통해 알 수 있듯이, 완전히 독립적인 두 state 변수와 Effect가 있음을 확인할 수 있다. 그것들은 우리가 동일한 외부 변수(네트워크의 연결 state)를 동기화했기 때문에 같은 시간에 같은 값을 가지고 있을 뿐이다.
 
같은 Hook을 호출하더라도 각각의 Hook 호출은 완전히 독립되어 있다. 이것이 위의 두 코드가 완전히 같은 이유다.
 
 

5. Custom Hook 작명법

Custom Hook의 이름은 코드를 자주 작성하는 사람이 아니더라도 커스텀 Hook이 무슨 일을 하고, 무엇을 props로 받고, 무엇을 반환하는지 알 수 있도록 아주 명확해야 한다.
 

  • ✅ useData(url)
  • ✅ useImpressionLog(eventName, extraData)
  • ✅ useChatRoom(options)

 
외부 시스템과 동기화할 때, Custom Hook의 이름은 아래 예시처럼 좀 더 기술적이고 해당 시스템을 특정하는 용어를 사용하는 것이 좋다.
 

  • ✅ useMediaQuery(query)
  • ✅ useSocket(url)
  • ✅ useIntersectionObserver(ref, options)

 
 
Costom Hook이 구체적인 목적에 집중할 수 있도록 하자. 또, 좋은 Costom Hook은 호출 코드가 하는 일을 제한하면서 좀 더 선언적으로 만들 수 있다.
 
예를 들어, useChatRoom(options)은 오직 채팅방과 연결할 수 있지만, useImpressionLog(eventName, extraData)은 애널리틱스에만 노출된 기록(Impression log)을 보낼 수 있다. Costom Hook API가 사용 사례를 제한하지 않고 너무 추상적이라면, 장기적으로는 그것이 해결할 수 있는 것보다 더 많은 문제를 만들 가능성이 높다.
 
 

6. Custom Hook으로 Effect를 감싸는 것이 종종 유용한 이유

  1. Effect로 주고받는 ‘데이터 흐름을 매우 명확하게’ 만들 때
  2. 컴포넌트가 Effect의 정확한 실행보다 ‘목적에 집중하도록’ 할 때
  3. React가 새 기능을 추가할 때, ‘다른 컴포넌트의 변경 없이 이 Effect를 삭제’할 수 있을 때

디자인 시스템과 마찬가지로, 앱의 컴포넌트에서 일반적인 관용구를 추출하여 Custom Hook으로 만드는 것이 도움이 될 수 있다. 이렇게 하면 컴포넌트의 코드가 의도에 집중할 수 있고, Effect를 자주 작성하지 않아도 된다.
 
 
 
 
 
 
 
 

728x90