React Hooks — 그것들이 뭔지, 주요 후크 및 사용 규칙

저자: IT Sectr 게시일: 2026-07-04 읽는 시간: 10 분

React Hooks는 클래스를 작성하지 않고 함수형 컴포넌트에서 상태, 효과, 컨텍스트 그리고 다른 React 기능을 사용할 수 있게 해주는 함수입니다. React 16.8에서 도입된 후크는 HOC와 render props와 같은 복잡한 패턴을 대체하여 개발 접법식을 근본적으로 변경시켰습니다. React, 2024에 따르면 모든 컴포넌트 관리 시나리오를 다루는 15개의 내장 후크가 있습니다.

주요 정리

  • 후크 — extends Component 없이 함수형 컴포넌트에 React 기능을 연결하는 함수입니다.
  • useState — 상태 관리를 위한 기본 후크, 값과 setter를 반환합니다.
  • useEffect — 부작용을 위한: API 요청, 구독, 타이머, DOM 조작.
  • useContext — Consumer 래퍼와 static contextType 없이 React 컨텍스트에 액세스하기 위한.
  • 후크 규칙 — 오직 최상위 레벨과 함수형 컴포넌트 또는 커스텀 후크 내부에서만 호출합니다.

React Hooks란 무엇인가?

React Hooks는 클래스를 만들지 않고 프레임웍의 내부 메커니즘에 “후크하는” 것이 가능한 React 내장 함수 집합입니다. React 16.8 이전에는 함수형 컴포넌트가 props만 받아들이고 JSX를 반환할 수 있었습니다 — 복잡한 로직은 클래스 컴포넌트가 필요했습니다.

클래스 컴포넌트의 문제는 관련 로직의 분할입니다. 하나의 엔티티와 관련된 코드(예: WebSocket 구독)가 componentDidMount, componentDidUpdate 그리고 componentWillUnmount 간에 강제로 분할되었습니다. 후크는 이를 해결합니다: 모든 로직이 하나의 useEffect에 그룹화됩니다.

두 번째 문제는 로직 재사용입니다. 클래스 컴포넌트 간에 상태 로직을 재사용하려면 HOC(고차 컴포넌트) 또는 render props를 적용해야 했습니다 — 둘 다 컴포넌트 트리에 추가 중첩을 만들었습니다. 커스텀 후크는 중첩 없이 이를 해결합니다.

useState — 상태 관리

useState는 가장 기본적인 후크입니다. 초기값을 받아 터플을 반환합니다: 현재 값과 것을 업데이트하는 함수입니다. 클래스의 this.setState와 달리 useState는 객체를 병합하지 않습니다 — 새 상태는 특정 useState 호출에 대해 이전 상태를 완전히 대체합니다.

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

function Form() {
  const [text, setText] = useState('');
  const [submitting, setSubmitting] = useState(false);

  async function handleSubmit() {
    setSubmitting(true);
    await fetch('/api/submit', {
      method: 'POST',
      body: JSON.stringify({ text })
    });
    setSubmitting(false);
  }

  return (
    <div>
      <input
        value={text}
        onChange={e => setText(e.target.value)}
      />
      <button onClick={handleSubmit}
        disabled={submitting}>
        {submitting ? 'Saving...' : 'Submit'}
      </button>
    </div>
  );
}

함수형 setState 형식

useState setter는 이전 값을 받아 새 값을 반환하는 함수를 수락할 수 있습니다. 함수형은 새 상태가 이전 상태에서 계산되고 백치화된 setState 호출이 가능할 때 필수입니다 — 각 호출은 현재 이전 값을 받게 됩니다.

useEffect — 부작용과 생명주기

useEffect는 함수형 컴포넌트에서 부작용을 수행하는 후크입니다. 두 가지 인수를 받습니다: 효과 함수와 의존성 배열입니다. React는 각 렌더링 후에 효과를 실행하지만 의존성이 지정된 경우에는 그중 하나가 변경될 때만 실행합니다.

빈 의존성 배열 []은 효과가 첫 렌더링 후에 한 번만 실행된다는 것을 의미합니다(componentDidMount와 유사). 효과에서 함수를 반환하면 언마운트 시(componentWillUnmount와 유사)와 효과 재실행 전에 실행되는 클리언업입니다.

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

function ChatRoom({ roomId }) {
  const [messages, setMessages] = useState([]);

  useEffect(() => {
    const socket = new WebSocket(`wss://chat/${roomId}`);

    socket.onmessage = (event) => {
      setMessages(prev => [...prev, JSON.parse(event.data)]);
    };

    // 클리언업 - 소켓 닫기
    return () => socket.close();
  }, [roomId]);

  return <ul>
    {messages.map((msg, i) =>
      <li key={i}>{msg.text}</li>
    )}
  </ul>;
}

useEffect의 의존성은 효과와 외부 세계 간의 유일한 연결입니다. 효과가 props 또는 상태의 변수를 사용하는 경우, 그 변수는 의존성 배열에 있어야 합니다. 이 규칙을 무시하면 스테일 클로저(stale closure)와 관련된 버그의 원인이 됩니다.

useContext — 컨텍스트 액세스

useContext는 컨텍스트 값을 읽기 위한 후크입니다. React.createContext로 만들어진 컨텍스트 객체를 받아 현재 값을 반환합니다. 컨텍스트가 변경되면 컴포넌트가 새 값으로 자동 련더링됩니다.

useContext 이전에는 함수형 컴포넌트에서 컨텍스트에 액세스하려면 Context.Consumer로 감싸야 했으며, 이로 인해 중첩이 발생했습니다. useContext는 Consumer를 완전히 제거합니다 — 함수형 컴포넌트 어디서 후크를 호출하면 됩니다.

js
import React, { createContext, useContext } from 'react';

const ThemeContext = createContext('밝은');

function ThemedButton() {
  const theme = useContext(ThemeContext);

  return <button
    className={`btn btn-${theme}`}
  >
    {theme === 'dark' ? 'moon' : '☀️'}
  </button>;
}

useContext는 Redux 또는 전역 상태의 대체빈이 아닙니다. React는 그리 자주 변경되지 않는 값— 테마, 로케일, 인증 상태에 대해 컨텍스트를 사용할 것을 권장합니다. 잦은 업데이트가 있는 데이터의 경우, 각 련더링마다 모든 컨텍스트 소비자가 재계산되어 성능이 저하될 수 있습니다.

useReducer — 복잡한 상태 로직

useReducer는 복잡한 업데이트 로직을 가진 상태를 위한 useState의 대안입니다. 리더스 함수와 초기 상태를 받아 현재 상태와 dispatch 함수를 반환합니다. 리더스는 현재 상태와 액션을 받아 새 상태를 반환합니다 — 부작용이 없는 순수 함수입니다.

useReducer는 여러 액션(set, reset, increment, addItem)이 하나의 상태를 관리할 때 특히 유용합니다. 다양한 setter를 가진 여러 useState 대신에 — 명확한 액션 타입으로 된 하나의 리더스로 대체합니다. 이로 인해 로직이 예측 가능하고 테스트하기 쉽습니다.

js
import React, { useReducer } from 'react';

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { ...state, count: state.count + 1 };
    case 'setStep':
      return { ...state, step: action.step };
    case 'reset':
      return { count: 0, step: 1 };
    default:
      throw new Error('Unknown action: ' + action.type);
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, {
    count: 0,
    step: 1
  });

  return (
    <>
      <p>Count: {state.count}</p>
      <button
        onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button
        onClick={() => dispatch({ type: 'reset' })}>Reset</button>
    </>
  );
}

useRef, useMemo, useCallback — 최적화

useRef는 렌더링 간에 지속되는 가변 객체를 만듭니다. current를 변경해도 재렌더링이 트리거되지 않습니다. 주요 용도는 DOM 요소에 액세스하고 가변 값(타이머, 이전 값, 라이브러리 인스턴스)을 저장하는 것입니다.

useMemo는 함수의 결과를 메모이즈합니다. 의존성이 변경될 때만 값을 재계산합니다. 비용이 많이 드는 계산에 사용하세요 — 큰 배열 필터링, 정렬, 데이터 포맷팅. useCallback은 함수를 메모이즈하기 위한 useMemo의 특별 사례입니다.

js
import React, { useRef, useMemo, useCallback } from 'react';

function SearchPage({ items, query, onItemSelect }) {
  const inputRef = useRef(null);

  // 마운트시 자동 포커스
  useEffect(() => {
    inputRef.current?.focus();
  }, []);

  // 필터링된 결과 메모이즈
  const filtered = useMemo(() => {
    return items.filter(item =>
      item.name.toLowerCase()
        .includes(query.toLowerCase())
    );
  }, [items, query]);

  // 콜백 메모이즈
  const handleSelect = useCallback((id) => {
    onItemSelect(id);
  }, [onItemSelect]);

  return (
    <div>
      <input ref={inputRef}
        value={query}
        onChange={e => setQuery(e.target.value)}
      />
      <ul>
        {filtered.map(item =>
          <li key={item.id}>
            <button
              onClick={() => handleSelect(item.id)}>
              {item.name}
            </button>
          </li>
        )}
      </ul>
    </div>
  );
}

중요한 규칙: useRef, useMemo 그리고 useCallback은 최적화 도구이지 필수 구조는 아닙니다. 모든 것을 useMemo로 감슸지 마세요 — 먼저 성능을 측정하세요. 조기 최적화는 코드를 복잡하게 만듭니다. React는 컴포넌트를 빠르게 렌더링합니다; useMemo는 계산이 정말 비싼 경우(O(n²) 이상)에만 필요합니다.

후크 규칙과 일반적인 실수

React는 후크에 대한 두 가지 엄격한 규칙을 도입했습니다. 첫째: 오직 최상위 레벨에서만 후크를 호출하세요 — 조건문, 반복문 또는 중첩된 함수 안에서는 호출하지 마세요. 둘째: React 함수형 컴포넌트 또는 커스텀 후크에서만 후크를 호출하세요. ESLint 플러그인 eslint-plugin-react-hooks이 두 규칙을 모두 자동으로 확인합니다.

가장 일반적인 실수는 후크 호출 순서 위반입니다. React는 렌더링 간의 후크 호출 순서에 이용합니다. 후크가 조건문 안에 있고 특정 렌더링에서 실행되지 않으면 다음 후크가 모두 밀려서 버그를 유발합니다.

실수발생하는 일해결법
조건문 안의 후크후크 순서가 깨짐조건 로직을 후크 내부로 이동
Stale closure효과가 고구 값 사용모든 의존성을 배열에 추가
무한 루프의존성 없는 useEffect가 state 변경의존성 지정 또는 useReducer 사용
render서 ref 변경함수 본문에서 부작용변경을 useEffect로 이동

두 번째 일반적인 실수는 useEffect에서의 stale closure입니다. 효과가 의존성에 지정되지 않은 변수를 사용하는 경우, 효과는 클로저 생성 시점의 값을 “캡처하게” 됩니다. 해결법은 사용된 모든 변수를 헥상 의존성 배열에 포함시키는 것입니다. 의존성이 너무 많으면 효과를 여러 개로 나누는 것을 고려하세요.

자주 묻는 질문

왜 후크를 조건문 안에서 호출할 수 없나요?

React는 렌더링 간의 후크 호출 순서에 의존합니다. 후크가 조건문 안에서 호출되고 특정 렌더링에서 실행되지 않으면 다음 후크가 모두 밀려서 그들의 상태가 섞이게 됩니다. ESLint 규칙 hooks/exhaustive-deps가 이에 대해 경고합니다.

useEffect와 useLayoutEffect의 차이점은 무엇인가요?

useLayoutEffect는 동기적이고 브라우저가 변경을 그리기 전에 실행됩니다. useEffect는 비동기적입니다 — 그린 후에 실행됩니다. useLayoutEffect는 사용자가 변경 사항을 보기 전에 DOM을 측정하거나 변경해야 할 때만 사용하세요(popover 위치, 스크롤 복원).

언마운트시 fetch 요청을 취소하는 방법은?

useEffect 내에서 AbortController를 사용하세요. controller를 만들고 signal을 fetch에 전달한 후 클리언업 함수에서 controller.abort()를 호출합니다. 구 브라우저의 경우 cancelled 플래그를 사용하세요: return () => { cancelled = true; } setState 전에 확인 조건을 포함합니다.

커스텀 후크란 무엇이고 언제 만드나요?

커스텀 후크는 내장 후크를 사용하는 use 접두사가 있는 JavaScript 함수입니다. 동일한 후크 로직이 두 개 이상의 컴포넌트에서 반복될 때 만드세요. 예: useDebounce, useLocalStorage, useMediaQuery. 커스텀 후크는 HOC와 render props를 대체합니다.

Strict Mode에서 useEffect가 두 번 실행되는 이유는?

Strict Mode에서 React는 개발 환경에서 청소되지 않은 효과와 관련된 버그를 찾기 위해 컴포넌트를 일부러 두 번 마운트 및 언마운트합니다. 클리언업이 올바르게 쓰였다면 중복 호출은 문제를 일으키지 않습니다. 프로덕션 모드에서는 이러한 일이 발생하지 않습니다.

요약

  • React Hooks — 클래스를 대체하여 함수형 컴포넌트에서 상태, 효과 그리고 컨텍스트를 사용하는 함수입니다.
  • useState — 안전한 업데이트를 위한 함수형 setter가 있는 로컬 상태의 기본 후크입니다.
  • useEffect — 의존성 배열과 클리언업 함수가 있는 부작용을 위한 보편 후크입니다.
  • useContext — Consumer 래퍼와 추가 중첩 없이 React 컨텍스트에 직접 액세스합니다.
  • useReducer — 단일 컴포넌트 내 Redux의 대안으로서 액션 타입으로 된 복잡한 상태 로직을 위한 것입니다.
  • useRef, useMemo, useCallback — 최적화 도구: DOM 참조, 값 및 콜백 메모이즈입니다.
  • 최상위 레벨 규칙 — 조건문, 반복문 및 중첩 함수 밖에서 후크를 호출하는 것에 대한 엄격한 제한입니다.

턴키 방식의 모바일 애플리케이션을 개발해 드립니다

IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.

프로젝트 논의

더 읽어보기