React - Custom Hook을 왜 쓸까?

Hunjin·2025년 11월 2일

SOPT

목록 보기
2/6
post-thumbnail

훅?! 많이 들어봤지만 좀 어렵기도 하고... 이걸 왜 쓰는지... 함수랑 뭐가 다른지... 어렵죠?^^

그럼 훅에 대해서 먼저 알아볼게요

Hook? 함수랑 뭐가 다른거지??

훅(Hook)과 일반 함수(Function)의 가장 큰 차이는 리액트의 기능을 사용할 수 있는지 여부입니다.

Hook

컴포넌트가 렌더링되는 동안에만 사용할 수 있는 특별한 함수

특징
1. 반드시 이름이 use로 시작해야 한다. (예: useState, useEffect, useGeolocation)
2. 리액트의 내부 기능(React State, Lifecycle features)을 사용할 수 있습니다.
3. 오직 함수형 리액트 컴포넌트 내부나 다른 커스텀 훅 내부에서만 호출할 수 있습니다.

훅은 컴포넌트의 렌더링과는 별개로 로직과 상태 관리를 담당합니다. 데이터를 가져오거나, 상태를 저장하거나, DOM에 직접 접근하는 등의 복잡한 작업을 수행합니다.

함수 (Function)

함수는 JavaScript의 기본적인 기능으로, 입력(인자)을 받아 특정 작업을 수행하고 값을 반환하는 단순한 코드 블록

특징

  • 리액트의 상태나 생명 주기 기능(useState, useEffect 등)을 직접 사용할 수 없습니다.
  • 어디서든 호출할 수 있습니다.

일반 함수는 데이터 계산, 문자열 처리, 외부 라이브러리 함수 호출 등 순수하게 JS, TS 영역에 속하는 작업을 수행합니다.


Custom Hook

다들 커스텀 훅에 대해서 한번씩은 들어보신 적 있죠?
훅도 잘 모르는데 커스텀 훅은 뭐지...

이번에는 커스텀 훅에 대해서 알아보겠습니다.

커스텀 훅은 말 그대로 개발자가 직접 만들어 사용하는 리액트 훅으로,
리액트의 내장 훅(useState, useEffect 등)을 이용해 특정 로직을 재사용 가능하게 만드는 함수입니다.

Custom Hook 장점

  • 코드 재사용성 : 여러 컴포넌트에서 동일한 로직을 재사용할 수 있다.
  • 관심사 분리 : 비즈니스 로직을 컴포넌트에서 분리하여 컴포넌트를 더 단순하게 만들어준다.
  • 테스트 용이성 : 로직을 독립적으로 테스트할 수 있다.
  • 유지보수성 : 로직이 한 곳에 모여있어 수정이 용이하다.

그럼 커스텀 훅을 왜 사용할까?

우선 제가 생각하는 커스텀 훅을 사용하는 가장 큰 이유는 장점 중 하나인 코드의 재사용성 때문입니다.
지금 제가 프로젝트에서 구현하고 있는 기능 중 하나인 GeoLocation API를 통해 사용자의 위치를 가져오는 기능을 구현하고 있는데 처음에는 하나의 컴포넌트에서만 사용하기 위해 커스텀 훅이 아닌 컴포넌트로 구현하였지만 개발이 진행되면서 유저의 위치를 가져오는 로직이 다른 컴포넌트에서도 동일하게 사용되는 경우가 있어 해당 로직을 커스텀 훅으로 변경하여 필요한 컴포넌트에서 함수 호출을 하여 사용하는 방식으로 변경하였습니다.

기존 컴포넌트 로직
[BEFORE] 위치 가져오기 로직 (LocationFetcher 컴포넌트)

import { useEffect } from 'react'

const LocationFetcher = () => {
  useEffect(() => {
    if(navigator.geolocation) {
      console.log("GeoLocation API 지원 확인");

      navigator.geolocation.getCurrentPosition (
        // 위치 요청 성공 여부에 따른 콜백 함수
        (position) => {
          const lat = position.coords.latitude // 위도
          const lng = position.coords.longitude // 경도

          console.log("사용자 현재 위치 가져오기 성공!");
          console.log(`위도 (Latitude): ${lat}`);
          console.log(`경도 (Longitude): ${lng}`);
        }, 
        (error) => {
          console.error("현재 위치 가져오기 실패", error.message);
          if (error.code === error.PERMISSION_DENIED) {
            console.warn("이유: 사용자가 위치 정보 접근을 거부했습니다.");
          } else if (error.code === error.POSITION_UNAVAILABLE) {
            console.warn("이유: 위치 정보를 사용할 수 없습니다.");
          } else if (error.code === error.TIMEOUT) {
            console.warn("이유: 위치 요청 시간이 초과되었습니다.");
          }
        }
      )
    }
  }, [])

  return null;
}

export default LocationFetcher

커스텀 훅으로 변경한 로직
[AFTER] 위치 가져오기 로직 (useGeolocation 커스텀 훅)

import { useState, useEffect } from 'react'

interface GeoLocationState {
  lat: number | null;
  lng: number | null;
  error: string | null;
}

const useGeolocation = () => {
  const [locationState, setLocationState] = useState<GeoLocationState>({
    lat: null,
    lng: null,
    error: null,
  })
  useEffect(() => {
    if(!navigator.geolocation) {
      console.error("현재 브라우저는 GeoLocation API를 지원하지 않습니다.");
      setLocationState(prev => ({
        ...prev,
        error: "현재 브라우저는 GeoLocation API를 지원하지 않습니다."
      }));
    }

    // 위치 요청 성공시 실행되는 콜백함수
    const successCallback = (postion: GeolocationPosition) => {
      const lat = postion.coords.latitude;
      const lng = postion.coords.longitude;

      console.log("사용자의 현재 위치 가져오기 성공");
      console.log(`위도 (Latitude): ${lat}, 경도 (Longitude): ${lng}`);

      setLocationState({lat,lng, error: null});
    };

    const errorCallback = (error: GeolocationPositionError) => {
      let errorMessage = `ERROR(${error.code}): ${error.message}`;
      if (error.code === error.PERMISSION_DENIED) {
        errorMessage = "사용자가 위치 정보 접근을 거부했습니다.";
      } else if (error.code === error.POSITION_UNAVAILABLE) {
        errorMessage = "위치 정보를 사용할 수 없습니다.";
      } else if (error.code === error.TIMEOUT) {
        errorMessage = "위치 요청 시간이 초과되었습니다.";
      }
      console.error(`useGeolocation: ${errorMessage}`);
      setLocationState(prev => ({
        ...prev,
        error: errorMessage,
      }));
    };
    // navigator.geolocation.getCurrentPosition(successCallback, errorCallback);
    navigator.geolocation.watchPosition(successCallback, errorCallback);
  }, []);
  return locationState;
};

export default useGeolocation;

이처럼 컴포넌트가 아닌 커스텀 훅으로 해당 로직을 변경하면서
이 기능을 사용하기 위해 다른 컴포넌트에서는 로직을 복사하는것이 아닌

import { useEffect, useRef, useState } from 'react'
import { loadNaverMapScript } from '../../utils/naverMapLoader'
import  useGeolocation  from "../../hooks/useLocation";

한줄로 import 하여 사용할 수 있습니다.

훅 사용 시 주의 사항

  • 클래스 기반 컴포넌트에서는 훅을 사용할 수 없음
  • 훅은 함수 컴포넌트의 최상위에서만 호출 가능
  • 훅은 항상 동일한 순서로 호출되어야 함
profile
프론트 개발을 해보아요👨🏻‍💻

0개의 댓글