본문으로 건너뛰기
프론트엔드 기초집LESSON 22

커스텀 Hook 만들기

난이도초급
예상 시간35분
선수지식이전 강의

22강. 커스텀 Hook 만들기

1. 이번 강의에서 해결할 문제

HomePage와 RankingsPage가 같은 게임 요청, 로딩, 오류, 재시도 코드를 반복합니다. UI와 비동기 로직을 분리해 재사용해야 합니다.

2. 학습 목표

  • 커스텀 Hook의 이름과 호출 규칙을 지킨다.
  • Effect와 state를 재사용 가능한 함수로 추출한다.
  • Hook이 반환할 최소 인터페이스를 설계한다.

3. 핵심 개념

커스텀 Hook은 React Hook을 조합하는 일반 함수이며 이름을 use로 시작합니다. Hook 호출 규칙은 컴포넌트와 동일하게 최상위에서만 호출합니다.

커스텀 Hook은 state 자체를 두 컴포넌트가 공유하게 하지 않습니다. 호출마다 독립된 state가 생깁니다. 같은 데이터를 정말 한 번만 요청해 공유해야 한다면 더 높은 부모에서 Hook을 한 번 호출하고 props로 내려야 합니다.

4. 단계별 코드 예시

파일 경로: game-release-dashboard/src/hooks/useGames.ts

import { useCallback, useEffect, useState } from 'react';
import { fetchGames } from '../api/games';
import type { Game } from '../types/game';

type RequestStatus = 'loading' | 'success' | 'error';

export function useGames() {
const [games, setGames] = useState<Game[]>([]);
const [status, setStatus] = useState<RequestStatus>('loading');
const [requestKey, setRequestKey] = useState(0);

const retry = useCallback(() => {
setRequestKey((key) => key + 1);
}, []);

useEffect(() => {
const controller = new AbortController();
setStatus('loading');

fetchGames(controller.signal)
.then((data) => {
setGames(data);
setStatus('success');
})
.catch((error: unknown) => {
if (error instanceof DOMException && error.name === 'AbortError') return;
console.error(error);
setStatus('error');
});

return () => controller.abort();
}, [requestKey]);

return { games, status, retry };
}

파일 경로: game-release-dashboard/src/pages/HomePage.tsx

import { useGames } from '../hooks/useGames';

export function HomePage() {
const { games, status, retry } = useGames();

if (status === 'loading') return <p>불러오는 중…</p>;
if (status === 'error') return <button onClick={retry}>다시 시도</button>;
return <main><h1>출시 예정 {games.length}</h1></main>;
}

5. 코드가 동작하는 이유

useGames가 요청에 필요한 state와 Effect를 감추고 페이지에는 게임, 상태, 재시도라는 도메인 인터페이스만 제공합니다. retry 함수는 같은 참조를 유지하고 requestKey가 바뀔 때만 Effect가 새 요청을 시작합니다.

6. 자주 하는 실수와 해결법

  • 일반 유틸 함수 이름을 use로 시작: 실제 Hook을 호출하는 재사용 로직에만 사용합니다.
  • 조건문에서 커스텀 Hook 호출: 컴포넌트 최상위에서 항상 호출합니다.
  • Hook이 JSX를 반환: 보통 로직과 값을 반환하고 화면은 컴포넌트가 담당하게 합니다.
  • 두 번 호출하면 자동 공유된다고 생각: 각 호출은 독립 state입니다.

7. 직접 실습

검색어를 localStorage에 저장하는 useStoredQuery Hook을 만드세요. 초기값을 읽고 query가 바뀔 때 저장하되 JSON 파싱 오류와 브라우저 저장소 접근 실패도 처리하세요.

8. 이해 점검 질문 3개

  1. 커스텀 Hook 이름이 use로 시작해야 하는 이유는 무엇인가요?
  2. 같은 Hook을 두 페이지에서 호출하면 state가 공유되나요?
  3. useGames를 분리했을 때 페이지 컴포넌트의 책임은 무엇만 남나요?

9. 핵심 요약

커스텀 Hook은 반복되는 state와 Effect 로직에 도메인 이름을 붙입니다. 페이지는 데이터의 사용 방법에 집중하고 요청 세부 사항은 한 곳에서 관리합니다.

MINI QUIZ

커스텀 Hook 만들기 미니 퀴즈

선택 즉시 정답과 해설을 확인할 수 있습니다. 결과는 이 브라우저에만 저장됩니다.

0 / 2
  1. 문제 1“커스텀 Hook 만들기” 실습 결과를 해석하는 올바른 기준은 무엇인가요?
  2. 문제 2“커스텀 Hook 만들기”의 작업 기준으로 ‘조건문에서 커스텀 Hook 호출’을 진단하거나 바로잡은 선택은 무엇인가요?
LESSON STATUS

학습을 마쳤나요?

직접 실습과 점검 질문까지 확인한 뒤 완료로 표시하세요.

22강. 커스텀 Hook 만들기 미완료 상태