카테고리 없음

React Hook과 Custom Hook 정리

워시기 2026. 7. 7. 12:04
반응형

React를 공부하다 보면 자주 만나게 되는 개념 중 하나가 Hook이다. Hook은 React 함수형 컴포넌트에서 상태 관리, 생명주기 처리, DOM 참조, 성능 최적화 같은 기능을 사용할 수 있게 해주는 문법이다.

예전 React에서는 컴포넌트의 상태나 생명주기 기능을 사용하기 위해 클래스형 컴포넌트를 많이 사용했다. 하지만 현재는 함수형 컴포넌트와 Hook을 사용하는 방식이 일반적이다. Hook을 사용하면 코드가 더 간결해지고, 기능별로 로직을 분리하기 쉬워진다.

Hook이란?

Hook은 함수형 컴포넌트 안에서 React의 기능을 사용할 수 있게 해주는 함수이다.

대표적인 Hook으로는 useState, useEffect, useRef, useMemo, useCallback 등이 있다.

Hook의 이름은 보통 use로 시작한다. 예를 들어 상태를 관리할 때는 useState, 화면 렌더링 이후 특정 작업을 처리할 때는 useEffect, 특정 값을 기억하거나 DOM 요소에 접근할 때는 useRef를 사용한다.

쉽게 말하면 Hook은 함수형 컴포넌트에서 필요한 기능을 가져와 사용할 수 있게 해주는 도구라고 볼 수 있다.

useState

useState는 React에서 가장 기본적으로 사용되는 Hook이다. 컴포넌트 내부에서 상태값을 관리할 때 사용한다.

상태값이란 화면에 영향을 주는 데이터라고 생각하면 된다. 예를 들어 버튼을 클릭할 때 숫자가 증가하거나, 입력창에 입력한 값이 화면에 표시되는 경우 해당 값은 상태로 관리할 수 있다.

import { useState } from "react";

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>현재 숫자: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        증가
      </button>
    </div>
  );
}

위 코드에서 count는 현재 상태값이고, setCount는 상태를 변경하는 함수이다. useState(0)에서 0은 초기값이다.

버튼을 클릭하면 setCount(count + 1)이 실행되고, count 값이 변경된다. 상태값이 변경되면 React는 해당 컴포넌트를 다시 렌더링하고, 변경된 값이 화면에 반영된다.

useEffect

useEffect는 컴포넌트가 렌더링된 이후 특정 작업을 실행할 때 사용하는 Hook이다.

예를 들어 API 호출, 이벤트 등록, 타이머 설정, 외부 라이브러리 연결, SignalR 연결 같은 작업을 처리할 때 사용할 수 있다.

import { useEffect } from "react";

function Example() {
  useEffect(() => {
    console.log("컴포넌트가 렌더링되었습니다.");

    return () => {
      console.log("컴포넌트가 사라질 때 정리됩니다.");
    };
  }, []);

  return <div>useEffect 예제</div>;
}

useEffect의 첫 번째 인자는 실행할 함수이고, 두 번째 인자는 의존성 배열이다.

의존성 배열이 비어 있는 [] 형태라면 컴포넌트가 처음 화면에 나타날 때 한 번만 실행된다. 그리고 return 안에 작성한 함수는 컴포넌트가 사라질 때 실행된다.

이 정리 함수는 매우 중요하다. 예를 들어 이벤트 리스너를 등록했거나, 타이머를 만들었거나, 서버 연결을 열었다면 컴포넌트가 사라질 때 정리해주어야 메모리 누수나 불필요한 동작을 막을 수 있다.

useRef

useRef는 값을 저장해두거나 DOM 요소에 직접 접근할 때 사용하는 Hook이다.

useState와 비슷하게 값을 보관할 수 있지만, useRef의 값이 변경되어도 화면이 다시 렌더링되지는 않는다. 그래서 렌더링과 상관없이 유지해야 하는 값을 저장할 때 유용하다.

import { useRef } from "react";

function Example() {
  const inputRef = useRef<HTMLInputElement | null>(null);

  const focusInput = () => {
    inputRef.current?.focus();
  };

  return (
    <div>
      <input ref={inputRef} />
      <button onClick={focusInput}>입력창 포커스</button>
    </div>
  );
}

위 코드에서는 inputRef를 사용해 input 요소에 접근하고 있다. 버튼을 클릭하면 focusInput 함수가 실행되고, 입력창에 포커스가 이동한다.

또한 useRef는 서버 연결 객체, 타이머 ID, 이전 값 저장, 컴포넌트 unmount 여부 확인 같은 상황에서도 자주 사용된다.

useMemo와 useCallback

useMemo와 useCallback은 성능 최적화와 관련된 Hook이다.

useMemo는 계산된 값을 기억해두는 Hook이다. 매번 렌더링될 때마다 비용이 큰 계산을 다시 하지 않도록 도와준다.

import { useMemo } from "react";

function Example({ numbers }: { numbers: number[] }) {
  const total = useMemo(() => {
    return numbers.reduce((sum, value) => sum + value, 0);
  }, [numbers]);

  return <p>합계: {total}</p>;
}

useCallback은 함수를 기억해두는 Hook이다. 컴포넌트가 다시 렌더링될 때마다 함수가 새로 만들어지는 것을 막고 싶을 때 사용한다.

import { useCallback } from "react";

function Example() {
  const handleClick = useCallback(() => {
    console.log("버튼 클릭");
  }, []);

  return <button onClick={handleClick}>클릭</button>;
}

다만 useMemo와 useCallback은 무조건 많이 사용한다고 좋은 것은 아니다. 코드가 복잡해질 수 있기 때문에 실제로 성능 문제가 있거나, 자식 컴포넌트의 불필요한 렌더링을 줄여야 할 때 사용하는 것이 좋다.

Hook 사용 규칙

React Hook을 사용할 때는 몇 가지 중요한 규칙이 있다.

첫 번째, Hook은 컴포넌트의 최상위에서만 호출해야 한다. 조건문, 반복문, 중첩 함수 안에서 Hook을 호출하면 안 된다.

// 잘못된 예
if (isOpen) {
  const [value, setValue] = useState("");
}

위와 같이 조건문 안에서 Hook을 사용하면 렌더링 시점마다 Hook 호출 순서가 달라질 수 있다. React는 Hook이 호출되는 순서를 기준으로 상태를 관리하기 때문에 호출 순서가 바뀌면 문제가 발생할 수 있다.

두 번째, Hook은 React 함수형 컴포넌트 또는 Custom Hook 안에서만 사용해야 한다. 일반 함수 안에서 마음대로 사용할 수 없다.

세 번째, Custom Hook의 이름은 반드시 use로 시작해야 한다. 예를 들어 useCounter, useSignalRConnection, useUserInfo 같은 이름을 사용한다.

Custom Hook이란?

Custom Hook은 React에서 제공하는 기본 Hook을 조합해서 내가 직접 만든 Hook이다.

컴포넌트 안에 상태 관리, 이벤트 처리, API 호출, 서버 연결 같은 로직이 많아지면 코드가 복잡해진다. 이때 관련 로직을 별도의 함수로 분리하면 컴포넌트 코드를 더 깔끔하게 관리할 수 있다.

이렇게 분리한 Hook을 Custom Hook이라고 한다.

쉽게 말하면 Custom Hook은 반복해서 사용할 수 있는 React 로직을 함수로 분리한 것이다.

Custom Hook을 사용하는 이유

Custom Hook을 사용하는 가장 큰 이유는 로직 재사용과 코드 분리이다.

예를 들어 여러 컴포넌트에서 같은 API 호출 로직을 사용해야 한다면, 매번 같은 코드를 작성하는 것은 비효율적이다. 이때 API 호출 로직을 Custom Hook으로 만들면 여러 컴포넌트에서 재사용할 수 있다.

또한 컴포넌트는 화면을 그리는 역할에 집중하고, 데이터 처리나 연결 관리 같은 로직은 Custom Hook으로 분리할 수 있다. 이렇게 하면 코드 구조가 더 명확해지고 유지보수하기 쉬워진다.

간단한 Custom Hook 예제

아래는 숫자를 증가, 감소, 초기화할 수 있는 useCounter Custom Hook 예제이다.

import { useState } from "react";

function useCounter(initialValue: number = 0) {
  const [count, setCount] = useState(initialValue);

  const increase = () => {
    setCount((prev) => prev + 1);
  };

  const decrease = () => {
    setCount((prev) => prev - 1);
  };

  const reset = () => {
    setCount(initialValue);
  };

  return {
    count,
    increase,
    decrease,
    reset,
  };
}

이제 컴포넌트에서는 다음과 같이 사용할 수 있다.

function CounterComponent() {
  const { count, increase, decrease, reset } = useCounter(0);

  return (
    <div>
      <p>현재 숫자: {count}</p>
      <button onClick={increase}>증가</button>
      <button onClick={decrease}>감소</button>
      <button onClick={reset}>초기화</button>
    </div>
  );
}

컴포넌트 입장에서는 내부에서 useState가 어떻게 동작하는지 자세히 알 필요 없이, useCounter가 제공하는 값과 함수만 사용하면 된다.

이처럼 Custom Hook을 사용하면 컴포넌트 코드가 훨씬 간단해진다.

Custom Hook과 일반 함수의 차이

Custom Hook도 결국 JavaScript 함수이다. 하지만 일반 함수와 다른 점은 React Hook을 내부에서 사용할 수 있다는 것이다.

일반 함수는 단순히 값을 계산하거나 특정 동작을 수행하는 데 사용된다.

function add(a: number, b: number) {
  return a + b;
}

반면 Custom Hook은 내부에서 useState, useEffect, useRef 같은 React Hook을 사용할 수 있다.

function useExample() {
  const [value, setValue] = useState("");

  useEffect(() => {
    console.log("value 변경:", value);
  }, [value]);

  return { value, setValue };
}

즉, Custom Hook은 React의 상태나 생명주기와 관련된 로직을 재사용하기 위해 만든 함수라고 볼 수 있다.

실무에서 Custom Hook이 사용되는 예

실무에서는 Custom Hook을 매우 자주 사용한다.

대표적으로 API 데이터를 불러오는 Hook을 만들 수 있다.

function useUserList() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    const fetchUsers = async () => {
      setLoading(true);

      try {
        const response = await fetch("/api/users");
        const data = await response.json();
        setUsers(data);
      } finally {
        setLoading(false);
      }
    };

    fetchUsers();
  }, []);

  return {
    users,
    loading,
  };
}

이렇게 만들면 컴포넌트에서는 다음과 같이 사용할 수 있다.

function UserList() {
  const { users, loading } = useUserList();

  if (loading) {
    return <p>로딩 중...</p>;
  }

  return (
    <ul>
      {users.map((user: any) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

컴포넌트는 화면을 표시하는 코드에 집중하고, 데이터를 가져오는 로직은 useUserList Hook이 담당한다.

또 다른 예로는 WebSocket이나 SignalR 연결을 관리하는 Hook도 만들 수 있다. 서버 연결, 재연결, 연결 상태 관리, 이벤트 핸들러 등록 같은 로직은 컴포넌트 안에 직접 작성하면 복잡해질 수 있다. 이런 경우 useSignalRConnection 같은 Custom Hook으로 분리하면 연결 관리 로직을 깔끔하게 관리할 수 있다.

Custom Hook을 만들 때 주의할 점

Custom Hook을 만들 때는 이름을 반드시 use로 시작해야 한다. 그래야 React Hook 규칙을 적용받을 수 있고, 다른 개발자도 해당 함수가 Hook이라는 것을 쉽게 알 수 있다.

또한 Custom Hook 안에서 사용하는 상태와 effect의 역할을 명확히 나누는 것이 좋다. 너무 많은 기능을 하나의 Hook에 넣으면 오히려 복잡해질 수 있다.

예를 들어 API 호출 Hook은 API 호출과 로딩 상태 관리에 집중하고, UI 표시 방식은 컴포넌트에서 처리하는 것이 좋다.

또한 useEffect 안에서 이벤트 리스너나 서버 연결을 등록했다면 반드시 정리 함수에서 해제해야 한다.

useEffect(() => {
  const handleResize = () => {
    console.log(window.innerWidth);
  };

  window.addEventListener("resize", handleResize);

  return () => {
    window.removeEventListener("resize", handleResize);
  };
}, []);

이렇게 정리 함수를 작성하면 컴포넌트가 사라질 때 불필요한 이벤트가 남아 있는 것을 방지할 수 있다.

Hook과 Custom Hook 정리

React Hook은 함수형 컴포넌트에서 상태 관리, 렌더링 이후 작업 처리, DOM 접근, 성능 최적화 같은 기능을 사용할 수 있게 해주는 함수이다.

useState는 상태 관리를 위해 사용하고, useEffect는 렌더링 이후 특정 작업을 처리할 때 사용한다. useRef는 렌더링과 상관없이 값을 유지하거나 DOM 요소에 접근할 때 사용한다. useMemo와 useCallback은 필요할 때 성능 최적화를 위해 사용한다.

Custom Hook은 이러한 Hook들을 조합해서 만든 사용자 정의 Hook이다. 반복되는 로직을 분리하고 재사용하기 위해 사용한다.

React에서 좋은 구조의 코드를 작성하려면 컴포넌트는 화면을 표현하는 역할에 집중하고, 상태 관리나 외부 연결 같은 복잡한 로직은 Custom Hook으로 분리하는 것이 좋다.

처음에는 Hook의 동작 방식이 어렵게 느껴질 수 있지만, useState, useEffect, useRef부터 차근차근 익히면 React 컴포넌트가 어떻게 동작하는지 더 명확하게 이해할 수 있다.

Custom Hook 꼭 use로 시작해야 하는지?

커스텀 훅의 이름은 반드시 use로 시작해야 한다.

React에서 Hook은 함수형 컴포넌트 안에서 상태 관리나 생명주기 처리 같은 기능을 사용할 수 있게 해주는 함수이다. 커스텀 훅 역시 React Hook을 조합해서 만든 함수이기 때문에 이름을 use로 시작하는 것이 규칙이다.

예를 들어 useCounter, useUserList, useSignalRConnection처럼 작성한다.

use로 시작하지 않아도 단순한 자바스크립트 함수처럼 보일 수는 있지만, 내부에서 useState, useEffect, useRef 같은 Hook을 사용한다면 반드시 커스텀 훅 규칙을 따라야 한다. 그래야 React와 ESLint가 해당 함수를 Hook으로 인식하고 Hook 사용 규칙을 올바르게 검사할 수 있다.

따라서 내부에서 React Hook을 사용하는 재사용 로직이라면 함수 이름을 use로 시작하는 커스텀 훅으로 작성하는 것이 맞다.

function useCounter() {
  const [count, setCount] = useState(0);

  return {
    count,
    setCount,
  };
}

올바른 커스텀 훅

useState, useEffect, useRef 사용 가능

 

function counterHook() {
  const [count, setCount] = useState(0);

  return {
    count,
    setCount,
  };
}

올바르지 못한, 커스텀 훅으로 보기 어렵다.

내부에서 useState, useEffect를 쓰면 Hook 규칙 검사에서 문제가 될 수 있음

반응형