React Hook 완전 이해하기

2026. 3. 21. 05:13·React

React Hook 완전 이해 - 단순한 함수가 아닌 React의 핵심 인터페이스

React를 사용하다 보면 자연스럽게 useState, useEffect, useRef와 같은 Hook을 접하게 됩니다.

대부분의 개발자는 Hook을 각각의 기능 단위 API로 이해하고 사용합니다.

 

예를 들어 useState는 상태 관리, useEffect는 사이드 이펙트 처리, useRef는 DOM 접근을 위한 도구라고 생각하는 경우가 많습니다.

 

이러한 이해 방식은 초반에는 큰 문제가 되지 않지만,

일정 수준 이상의 복잡한 상태 관리나 렌더링 최적화를 다루게 되면 한계에 부딪히게 됩니다.

 

Hook을 단순히 기능으로만 이해할 경우, 언제 어떤 Hook을 사용해야 하는지에 대한 기준이 흐려지고 불필요한 useEffect 사용이나 잘못된 상태 설계로 이어지기 쉽습니다.

 

Hook을 제대로 이해하기 위해서는 관점을 바꿔야 합니다.

Hook은 단순한 기능 제공 API가 아니라, React의 렌더링 시스템과 연결되는 인터페이스입니다.

다시 말해, Hook은 React 내부에서 관리되는 상태와 생명주기 흐름에 접근할 수 있도록 해주는 진입점이라고 볼 수 있습니다.

 

이 글에서는 Hook을 단순 사용법이 아닌, React의 내부 동작과 렌더링 과정이라는 관점에서 정리해보겠습니다.


Hook이 등장한 이유

React 초창기에는 상태와 생명주기를 관리하기 위해 클래스 컴포넌트를 사용했습니다.

class Counter extends React.Component {
  state = { count: 0 };

  render() {
    return <div>{this.state.count}</div>;
  }
}
 

클래스 컴포넌트는 분명 강력한 기능을 제공했지만, 실제로 개발을 진행하면서 여러 가지 문제가 드러났습니다.

가장 대표적인 문제는 로직 재사용이 어렵다는 점이었습니다. 동일한 상태 관리나 생명주기 로직을 여러 컴포넌트에서 재사용하려면 HOC나 Render Props와 같은 패턴을 사용해야 했고, 이는 코드 구조를 복잡하게 만드는 원인이 되었습니다.

 

또한 this 바인딩 문제 역시 개발 경험을 떨어뜨리는 요소였습니다. 이벤트 핸들러를 작성할 때마다 this를 신경 써야 했고, 이는 불필요한 인지 부담을 증가시켰습니다. 시간이 지날수록 컴포넌트 내부에는 상태, 생명주기, 이벤트 로직이 뒤섞이게 되었고, 결과적으로 코드의 가독성과 유지보수성이 크게 떨어졌습니다.

 

이러한 문제를 해결하기 위해 React 팀은 함수 컴포넌트에서도 상태와 생명주기를 사용할 수 있도록 하는 새로운 방식, 즉 Hook을 도입했습니다.

function Counter() {
  const [count, setCount] = useState(0);
  return <div>{count}</div>;
}
 

Hook의 도입으로 인해 함수 컴포넌트에서도 상태를 직접 관리할 수 있게 되었고, 로직을 함수 단위로 분리하여 재사용하는 것이 훨씬 자연스러워졌습니다.


Hook의 정의를 다시 생각해보기

공식 문서에서는 Hook을 “함수 컴포넌트에서 React 기능을 사용할 수 있게 해주는 함수”라고 정의합니다. 이 정의는 틀린 말은 아니지만, Hook의 본질을 충분히 설명해주지는 못합니다.

 

보다 본질적인 관점에서 Hook은 다음과 같이 정의할 수 있습니다.

Hook은 React 내부에서 관리되는 상태와 생명주기 로직에 접근할 수 있도록 해주는 인터페이스입니다.

 

이 정의가 중요한 이유는 Hook을 바라보는 기준을 바꿔주기 때문입니다. Hook을 단순한 기능이 아니라 React 내부 시스템과 연결되는 통로로 이해하게 되면, 각 Hook이 언제, 왜 필요한지에 대한 판단 기준이 명확해집니다.


React 렌더링과 Hook의 관계

Hook을 이해하기 위해서는 React의 렌더링 방식에 대한 이해가 반드시 필요합니다. React에서 함수 컴포넌트는 상태가 변경될 때마다 다시 실행됩니다.

function Component() {
  const [count, setCount] = useState(0);
  return <div>{count}</div>;
}

 

setCount가 호출되면 컴포넌트 함수는 처음부터 다시 실행되고, 함수 내부에 선언된 모든 변수는 새롭게 생성됩니다. 일반적인 자바스크립트 함수라면 이 과정에서 이전 값이 모두 사라져야 합니다.

 

하지만 count 값은 유지됩니다. 이는 React가 상태를 함수 내부가 아니라 외부에서 별도로 관리하고 있기 때문입니다.

함수 컴포넌트는 단순히 UI를 계산하는 역할을 수행하고, 상태는 React가 별도의 구조를 통해 보존합니다.

 

이 지점에서 Hook의 역할이 드러납니다. Hook은 함수 컴포넌트가 다시 실행될 때마다 React가 저장하고 있는 상태에 접근할 수 있도록 연결해주는 역할을 합니다.


Hook의 동작 원리

React는 Hook을 내부적으로 순서 기반으로 관리합니다. 이를 단순화하여 표현하면, React는 Hook을 배열 형태로 저장하고 현재 호출 위치를 index로 관리한다고 볼 수 있습니다.

let hooks = [];
let index = 0;
 

컴포넌트가 렌더링될 때마다 Hook이 호출되는 순서대로 상태가 매핑됩니다.

useState(); // index 0
useEffect(); // index 1
useState(); // index 2
 

이 구조에서 중요한 점은 Hook이 이름이 아니라 호출 순서로 식별된다는 것입니다. React는 “첫 번째 useState”, “두 번째 useEffect”와 같이 순서를 기준으로 상태를 기억합니다.


Hook 규칙이 존재하는 이유

React에서는 Hook 사용에 대해 몇 가지 규칙을 강하게 요구합니다. 대표적으로 Hook은 항상 컴포넌트의 최상단에서 호출해야 하며, 조건문이나 반복문 내부에서 호출해서는 안 됩니다.

이러한 규칙은 단순한 스타일 가이드가 아니라, 내부 동작 원리에서 비롯된 필수 조건입니다.

예를 들어 다음과 같은 코드가 있다고 가정해보겠습니다.

if (flag) {
  useState(0);
}
 

이 경우 렌더링 시점마다 Hook의 호출 순서가 달라질 수 있습니다. 어떤 렌더에서는 useState가 호출되고, 어떤 렌더에서는 호출되지 않게 되면서 React가 상태를 잘못된 위치에 매핑하게 됩니다.

 

그 결과, 이전 렌더에서 사용되던 상태가 다른 Hook에 연결되는 문제가 발생하게 됩니다. 이러한 문제를 방지하기 위해 Hook은 항상 동일한 순서로 호출되어야 하며, 이를 보장하기 위한 규칙이 존재하는 것입니다.


Hook을 렌더링 관점에서 이해하기

Hook을 기능 중심이 아니라 렌더링 관점에서 바라보면 훨씬 명확하게 정리할 수 있습니다.

 

useState는 상태를 저장하고, 해당 상태가 변경되면 렌더링을 다시 발생시키는 역할을 합니다.

즉, 렌더링을 트리거하는 데이터입니다.

 

useEffect는 렌더링이 완료된 이후 실행됩니다. 주로 API 호출이나 DOM 조작과 같이 외부 시스템과의 동기화 작업을 담당합니다.

 

useRef는 값은 유지하지만 렌더링에는 영향을 주지 않는 저장소입니다. 렌더링과 무관하게 값을 유지해야 할 때 사용됩니다.

 

이처럼 각 Hook은 React의 렌더링 과정에서 특정 시점과 역할을 담당합니다. 따라서 Hook을 이해할 때는 “무슨 기능인가”보다 “렌더링과 어떤 관계인가”를 기준으로 보는 것이 중요합니다.


Hook을 잘 사용하기 위한 판단 기준

Hook을 잘 사용하기 위한 기준은 다음과 같습니다.

 

렌더링에 영향을 주는가?

먼저, 어떤 값이 렌더링에 영향을 주는지 판단합니다. 만약 UI에 영향을 주는 값이라면 state로 관리해야 하고, 그렇지 않다면 ref를 사용하는 것이 적절합니다.

 

올바르지 않은 예시

function Timer() {
  const [intervalId, setIntervalId] = useState(null);

  useEffect(() => {
    const id = setInterval(() => {
      console.log("tick");
    }, 1000);

    setIntervalId(id);
  }, []);

  return <div>타이머 실행 중</div>;
}

 

이 경우 intervalId는 UI에 전혀 사용되지 않습니다.
하지만 state로 관리하고 있기 때문에 불필요한 렌더링이 발생합니다.

 

옳은 예시

function Timer() {
  const intervalRef = useRef(null);

  useEffect(() => {
    intervalRef.current = setInterval(() => {
      console.log("tick");
    }, 1000);
  }, []);

  return <div>타이머 실행 중</div>;
}

이처럼 렌더링과 무관한 값은 ref로 관리하는 것이 적절합니다.

 

외부 시스템과 연결되는가?

또한 해당 로직이 외부 시스템과 연결되는지 여부를 판단합니다. 서버와의 통신, DOM 조작, 이벤트 구독과 같은 작업이라면 effect를 사용하는 것이 맞지만, 단순한 계산 로직이라면 effect를 사용하는 것은 오히려 잘못된 설계가 될 수 있습니다.

 

올바르지 않은 예시

function Example({ a, b }) {
  const [sum, setSum] = useState(0);

  useEffect(() => {
    setSum(a + b);
  }, [a, b]);

  return <div>{sum}</div>;
}

 

이 코드는 단순 계산을 위해 useEffect를 사용하고 있습니다.
이 경우 불필요한 상태와 effect가 추가된 구조입니다.

 

옳은 예시 (계산은 렌더에서)

function Example({ a, b }) {
  const sum = a + b;
  return <div>{sum}</div>;
}

단순 계산은 렌더 과정에서 처리하는 것이 더 자연스럽습니다.

 

옳은 예시 (useEffect가 필요한 경우 / 외부 시스템)

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    fetch(`/api/user/${userId}`)
      .then(res => res.json())
      .then(setUser);
  }, [userId]);

  return <div>{user?.name}</div>;
}

이처럼 서버 요청처럼 외부 시스템과 연결되는 경우에만 useEffect를 사용하는 것이 적절합니다.

 

상태 변경 로직이 복잡한가?

상태 변경 로직이 복잡해지는 경우에는 useReducer를 통해 상태 전이를 명확하게 관리하는 것이 더 좋은 선택이 될 수 있습니다.

 

예를 들어,

function Form() {
  const [name, setName] = useState("");
  const [email, setEmail] = useState("");
  const [isSubmitting, setIsSubmitting] = useState(false);

  const handleSubmit = () => {
    setIsSubmitting(true);
    // ... 로직
    setIsSubmitting(false);
  };

  return (
    <>
      <input value={name} onChange={e => setName(e.target.value)} />
      <input value={email} onChange={e => setEmail(e.target.value)} />
    </>
  );
}

상태가 많아질수록 관리 포인트가 늘어나고, 로직이 분산됩니다.

 

useReducer의 사용

function reducer(state, action) {
  switch (action.type) {
    case "SET_NAME":
      return { ...state, name: action.payload };
    case "SET_EMAIL":
      return { ...state, email: action.payload };
    case "SUBMIT":
      return { ...state, isSubmitting: true };
    default:
      return state;
  }
}

function Form() {
  const [state, dispatch] = useReducer(reducer, {
    name: "",
    email: "",
    isSubmitting: false,
  });

  return (
    <>
      <input
        value={state.name}
        onChange={e =>
          dispatch({ type: "SET_NAME", payload: e.target.value })
        }
      />
      <input
        value={state.email}
        onChange={e =>
          dispatch({ type: "SET_EMAIL", payload: e.target.value })
        }
      />
    </>
  );
}

상태 변화가 “의도(액션)” 중심으로 관리되면서 로직이 훨씬 명확해집니다.

 

이러한 기준이 생기면 불필요한 Hook 사용이 줄어들고, 코드의 구조와 가독성이 크게 개선됩니다.


정리

Hook은 단순한 기능을 제공하는 API가 아니라, React 렌더링 시스템에 접근할 수 있도록 해주는 인터페이스입니다.

Hook을 이해한다는 것은 곧 React가 어떤 방식으로 상태를 관리하고, 언제 렌더링이 발생하며, 어떤 시점에 어떤 로직이 실행되는지를 이해하는 것과 같습니다.

 

이 관점을 기반으로 Hook을 사용하게 되면 불필요한 useEffect를 줄일 수 있고, 상태 설계를 더 명확하게 할 수 있으며, 결과적으로 더 예측 가능한 컴포넌트를 작성할 수 있습니다.

 

Hook에 대한 기본적인 정보를 다뤘으니 다음 시간에는

- useState

- useEffect

- useRef

- useReducer

에 대한 상세 정보와 활용 예시에 대해서 다루어보려고 합니다.

 

 

감사합니다!

'React' 카테고리의 다른 글

TanStack Query 에 대해서 알아보기!  (0) 2026.04.04
상태 관리 전략 비교 - Context API, Zustand, Redux Toolkit 중 무엇을 선택해야 할까?  (0) 2026.03.23
React useState 완전 이해하기  (0) 2026.03.21
커스텀 훅과 책임 분리  (0) 2026.03.16
React에서 컴포넌트를 어떻게 설계할 것인가?  (0) 2026.03.09
'React' 카테고리의 다른 글
  • 상태 관리 전략 비교 - Context API, Zustand, Redux Toolkit 중 무엇을 선택해야 할까?
  • React useState 완전 이해하기
  • 커스텀 훅과 책임 분리
  • React에서 컴포넌트를 어떻게 설계할 것인가?
수달군
수달군
  • 수달군
    수달 코딩 공장
    수달군
  • 전체
    오늘
    어제
    • 분류 전체보기 (21)
      • React (10)
      • Next.js (7)
      • TypeScript 딥 다이브! (1)
      • 웹 기초 이론 (0)
      • 코딩 테스트 준비 (1)
        • Python 기본 (1)
      • AI 도구들 (0)
      • 프로젝트 회고 (0)
      • 자료구조 (0)
      • 일상 (0)
      • 해외 여행 (0)
      • 국내 여행 (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • 깃허브
  • 공지사항

  • 인기 글

  • 태그

    입력값
    파이썬 초보
    입력받기
    python
    입력
    input
    coding
    파이썬
    코딩
    it
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
수달군
React Hook 완전 이해하기
상단으로

티스토리툴바