왜 필요한가 만드는 법 사용하기 규칙 흔한 예시 📝 문제풀이
◀ 이전 📋 목차 CH 12 ▶
🪝 CHAPTER 11 · 고급

커스텀 훅 — 내가 만드는 나만의 훅

여러 컴포넌트에서 똑같은 상태 로직을 복사·붙여넣기 하고 있나요? 그렇다면 커스텀 훅을 만들 때예요. 이번 장은 "로직을 재사용하는 기술"을 배웁니다. 생각보다 아주 간단해요.

🎯 이 장을 끝내면
🧩
이 장에서 뭐가 JS고 뭐가 리액트? 🟦 순수 JS: 반복되는 코드를 함수로 뽑아 재사용하는 것 자체, 반환값을 구조 분해·스프레드로 주고받는 문법. 🟪 리액트: 이름을 use~로 짓고 그 안에서 다른 훅(useState·useEffect)을 호출하는 커스텀 훅 규칙. "그냥 함수인데 내부에서 훅을 쓰면 커스텀 훅"이에요. 값을 계산·판단하면 JS, "화면·컴포넌트에 어떻게 반영되나"는 리액트라고 보면 대체로 맞아요.
🤔
왜 필요한가
"똑같은 로직"을 또 복사하고 있다면
커스텀 훅은 컴포넌트가 아니라 "로직"을 재사용하는 도구예요.

여러 컴포넌트를 만들다 보면 똑같은 상태 로직이 자꾸 반복돼요. 예를 들어 입력값 관리(값 담을 state + onChange), 데이터 페칭(로딩·에러·데이터 상태), 창 크기 감지(resize 이벤트 등록/해제) 같은 것들이죠. 이걸 컴포넌트마다 복사·붙여넣기 하면 관리가 지옥이 됩니다.

🔤
데이터 페칭(fetching) — "페칭"은 서버에서 데이터를 가져오는 것을 말해요(fetch = 가져오다). 보통 가져오는 동안은 "로딩 중", 실패하면 "에러", 성공하면 "데이터"—이 세 가지 상태를 함께 다뤄요.
🧩 컴포넌트 재사용(UI) vs 커스텀 훅(로직)🟪 리액트 우리는 이미 컴포넌트UI(화면 조각)를 재사용하는 법을 배웠어요. 하지만 컴포넌트는 "보이는 것"을 재사용하죠. 반면 커스텀 훅은 "보이지 않는 상태 로직"을 재사용해요. 핵심 한 줄: 컴포넌트 = UI 재사용, 커스텀 훅 = 로직 재사용. 이 구분만 확실히 잡으면 이번 장은 끝난 거나 마찬가지예요.
커스텀 훅 로직 재사용 use 접두사 useInput useToggle useFetch 독립 상태
🧭
커스텀 훅은 새로운 문법이 아니에요. 우리가 아는 useState·useEffect함수 하나로 묶어 이름을 붙인 것뿐이에요. 그래서 앞 장들을 이해했다면 이번 장은 아주 쉽습니다.
🛠️
만드는 법
use로 시작하는 함수를 만든다
그 안에서 useState·useEffect 같은 훅을 자유롭게 써요.

커스텀 훅을 만드는 방법은 놀라울 만큼 단순해요. 이름이 use로 시작하는 함수를 하나 만들고, 그 안에서 useState·useEffect 같은 기본 훅을 사용한 뒤, 컴포넌트가 필요로 하는 값을 return하면 됩니다.

🏷️
① 이름 짓기
use...로 시작하는 함수
🪝
② 안에서 훅 쓰기
useState·useEffect
🎁
③ 값 돌려주기
필요한 값을 return
// useCounter.js — 카운터 로직을 담은 커스텀 훅
import { useState } from 'react';

function useCounter(초기값) {
  const [count, setCount] = useState(초기값);

  const inc = () => setCount(c => c + 1);   // 1 증가
  const dec = () => setCount(c => c - 1);   // 1 감소
  const reset = () => setCount(초기값);      // 초기값으로

  // 컴포넌트가 쓸 값들을 객체로 묶어 반환
  return { count, inc, dec, reset };
}

export default useCounter;
💡
딱 3단계. ① 이름을 use...로 짓는다 → ② 안에서 기본 훅을 쓴다 → ③ 필요한 값을 return한다. 이게 전부예요. 별도의 특별한 API가 있는 게 아닙니다.
🔍 왜 함수 하나로 묶는 걸까?🟦 순수 JSuseCounter"숫자를 세는 데 필요한 state와 함수들"을 한 덩어리로 묶었어요. 이제 카운터가 필요한 컴포넌트는 이 훅 한 줄만 호출하면 되고, useState와 증감 함수를 매번 다시 쓸 필요가 없어요.
📦
사용하기
컴포넌트 안에서 한 줄로 호출
호출할 때마다 "새로운, 독립된 상태"가 생겨요.

만든 커스텀 훅은 일반 함수 호출처럼 컴포넌트 안에서 부르면 돼요. 반환된 값을 구조 분해로 받아 쓰면 깔끔합니다.

🔤
구조 분해(destructuring) — 객체·배열에 담긴 값을 한 번에 풀어서 각각의 변수로 받는 문법이에요. 예: const { count, inc } = useCounter(0) 는 반환된 객체에서 count·inc를 콕 집어 꺼내 쓰는 거예요.
import useCounter from './useCounter';

function Counter() {
  // 한 줄로 카운터 로직을 그대로 가져옴
  const { count, inc, dec, reset } = useCounter(0);

  return (
    <div>
      <p>현재 값: {count}</p>
      <button onClick={inc}>+1</button>
      <button onClick={dec}>-1</button>
      <button onClick={reset}>리셋</button>
    </div>
  );
}
⚠️ 가장 헷갈리는 지점 — 상태는 "공유"되지 않아요🟪 리액트 두 컴포넌트가 같은 useCounter를 써도, 상태를 공유하지 않아요. 커스텀 훅은 호출될 때마다 그 안의 useState가 새로 실행돼서, 각 컴포넌트는 자기만의 독립된 count를 가집니다. 커스텀 훅은 "로직(코드)"을 공유하는 것이지 "상태(값)"를 공유하는 게 아니에요. 상태를 여러 컴포넌트가 함께 쓰려면 Context(CH 10)나 상태관리 도구가 필요해요.
🅰️
A 컴포넌트
useCounter(0) 호출
🧮
각자 새 state
호출마다 독립
🅱️
B 컴포넌트
useCounter(0) 호출
📏
규칙
이름은 반드시 use로 시작
반환값 형태는 자유예요.

커스텀 훅에는 딱 하나 꼭 지켜야 할 이름 규칙이 있어요. 반드시 이름이 use로 시작해야 합니다. 그래야 리액트(와 린트 도구 — 코드 실수를 자동으로 잡아주는 검사기)가 "이건 훅이다"라고 인식하고, 훅 규칙(조건문·반복문 안에서 훅을 쓰지 말 것 등)을 검사해줘요.

✅ 왜 use 접두사가 필수일까🟪 리액트 리액트는 함수 이름만 보고 훅 여부를 판단해요. useCounter는 훅으로 인식하지만, getCounter처럼 지으면 일반 함수로 취급돼 훅 규칙 검사를 못 받아요. 훅 규칙을 어겨도 경고가 안 떠서 버그를 놓치기 쉬워집니다. 규칙: 함수 이름이 use로 시작 + 안에서 다른 훅을 사용 → 이게 바로 커스텀 훅이에요.
규칙내용
이름반드시 use로 시작. (useInput, useToggle …) 그래야 훅으로 인식돼요.
반환값자유예요. 값 하나, 배열, 객체 무엇이든 OK. 필요에 맞게 골라요.
내부안에서 useState·useEffect기본 훅을 자유롭게 쓸 수 있어요.
훅 규칙일반 훅과 동일 — 최상위에서만 호출(조건문·반복문 안 금지).
🎁
반환값 형태 팁. 값이 여러 개면 순서·이름을 바꿔 받고 싶을 때는 배열(예: useState처럼 [값, 함수]), 여러 값을 이름으로 골라 쓰고 싶을 때는 객체가 편해요. 정답은 없고 상황에 맞게 고르면 됩니다.
🧰
흔한 예시
실무에서 자주 쓰는 커스텀 훅들
이름만 봐도 "무슨 로직인지" 감이 오죠?
① useInput — 입력값 관리 valueonChange를 묶어, 폼 입력 하나를 한 줄로 관리해요. 입력 필드가 많은 폼에서 특히 유용해요.
function useInput(초기값) {
  const [value, setValue] = useState(초기값);
  const onChange = (e) => setValue(e.target.value);
  return { value, onChange };   // 그대로 input에 펼쳐 쓰기 좋음
}

// 사용: const name = useInput('');
//       <input value={name.value} onChange={name.onChange} />
② useToggle — 참/거짓 뒤집기 모달 열기/닫기, on/off 스위치처럼 true ↔ false를 오가는 로직을 담아요.
function useToggle(초기값 = false) {
  const [on, setOn] = useState(초기값);
  const toggle = () => setOn(prev => !prev);   // 현재 값을 뒤집음
  return [on, toggle];   // 배열로 반환 → 이름 자유롭게 받기
}

// 사용: const [열림, 토글] = useToggle();
③ useFetch — 데이터 페칭🟦 순수 JS🟪 리액트 useEffect 안에서 API를 호출하고 로딩·에러·데이터 상태를 함께 관리하는, 아주 자주 만드는 훅이에요.
function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    setLoading(true);
    fetch(url)
      .then(res => res.json())
      .then(setData)
      .catch(setError)
      .finally(() => setLoading(false));
  }, [url]);   // url이 바뀌면 다시 가져옴

  return { data, loading, error };
}
🌱
핵심 패턴은 똑같아요. 셋 다 "안에서 기본 훅을 쓰고 → 필요한 값을 return"하는 구조예요. 어떤 로직이든 반복된다면 use~ 함수로 뽑아내는 습관을 들이면 코드가 훨씬 깔끔해집니다.
🧠 이 장 핵심 요약
📝
문제풀이 · 점검
배운 걸 가볍게 점검해봐요
시험이 아니라 "내가 이해했나" 확인용이에요. 틀려도 바로 해설이 나와요.
🧪
문제를 풀면 즉시 정답과 해설이 나오고, 위쪽 바에 점수가 쌓여요. 네 가지 유형(객관식 · O/X · 빈칸 · 개념결과)을 섞어 두었어요.
CH 12 ⚡ 성능 최적화 — 불필요한 렌더링을 줄이는 기술들