React 기초 · 2장 훅 기초

2장 · 훅(Hook) 기초

먼저 가장 많이 쓰는 React 내장 훅 6가지 (useState?useEffect?(+정리 함수) → useRef?useMemo?useCallback?useContext?) 를 직접 실행하며 익힙니다. 훅은 이게 전부가 아니에요 — 아래 "훅 전체 지도"에서 프로젝트가 쓰는 모든 훅을 한눈에 봅니다.

훅이 뭔가요?

훅(Hook)?use 로 시작하는 특별한 함수예요. 함수형 컴포넌트?상태? 를 기억하고, 부수 효과? 를 다루도록 React 기능에 갈고리를 거는 도구입니다.

훅의 두 가지 규칙

① 컴포넌트(또는 커스텀 훅)의 맨 위에서 호출한다. ② if·반복문 안에서 호출하지 않는다.
React는 훅이 항상 같은 순서로 불린다고 믿고 값을 기억하기 때문이에요.

훅 전체 지도 — "이게 다가 아니에요"

이 장에서 배우는 6개는 가장 기본이 되는 React 내장 훅일 뿐입니다. 실제 이 학습 프로젝트는 내장 훅 외에 라이브러리 훅직접 만든 커스텀 훅(약 195개!) 을 아주 많이 씁니다. 어디서 배우는지까지 정리했어요.

① React 내장 훅 (React가 기본 제공)

쓰임이 사이트에서
useState바뀌는 값이 장 2.1
useEffect부수 효과 + 정리이 장 2.2 · 2.3
useRefDOM 접근 / 숨은 값이 장 2.4
useMemo값 캐싱이 장 2.5
useCallback함수 캐싱이 장 2.6
useContextprops 없이 값 공유이 장 2.7
useId?고유 id 생성이 장 2.8
useImperativeHandle?자식 기능 노출(고급)이 장 2.8
useReducer?복잡한 state(이 프로젝트선 드묾)2.8에서 언급

② 라이브러리 훅 (설치한 패키지가 제공)

출처쓰임배우는 곳
useQuery · useMutation · useQueryClientReact Query?서버 데이터 조회/변경/캐시4장
useStore (create)Zustand?전역 상태4장
useForm · useWatch · useFieldArray · useFormContextreact-hook-form?폼 값·검증·제출5장
useNavigate · useParamsTanStack Router?화면 이동 / 주소 값5장
useSignalPreact Signals세밀한 상태(필터·정렬 등)4장에서 언급
useSensor · useSortablednd-kit드래그&드롭참고용
useDropzonereact-dropzone파일 업로드참고용
useSidebar · useFormFieldShadcn UIUI 컴포넌트 내부7장

③ 커스텀 훅 (이 프로젝트가 직접 만든 것 — 약 195개)

커스텀 훅?use 로 시작하는 내가 만든 함수예요. 반복되는 훅 로직을 모아 재사용합니다. 이 프로젝트에서 가장 많이 쓰는 종류:

예시역할(이름 패턴)
useAuth (46회)로그인 사용자 정보·권한 꺼내기
useSearchForm (23회)검색 폼 상태 관리 공통 로직
useEquipmentPageStore, useUserPageStoreuse…PageStore : 페이지별 화면 상태 store
useUserListQuery, useTestReqQueryuse…Query : 특정 데이터 조회 useQuery 묶음
useProjectAction, useRecipeActionuse…Action : 저장·삭제 등 동작 모음
정리

훅 = 내장 훅(이 장) + 라이브러리 훅(4·5·7장) + 커스텀 훅(3장에서 만드는 법). 이 장의 6개는 나머지 모든 훅의 토대라서 가장 먼저, 가장 탄탄히 익히는 거예요.

2.1 useState — "바뀌면 화면도 바뀌는 변수"

일반 변수 let count = 0 은 값을 바꿔도 화면이 그대로입니다. useState 로 만든 값은 바꾸면 React가 자동으로 화면을 다시 그립니다(리렌더?).

useState 는 화이트보드에 적힌 숫자. setCount 로 고치면 보드가 자동으로 새로 그려집니다.

아래 코드에서 초기값 010 으로 바꾸고 ▶ 실행 해보세요.

꼭 지킬 것

값을 count = 1 처럼 직접 바꾸면 안 됩니다. 반드시 setCount(1).
이전 값을 기반으로 바꿀 땐 함수형 업데이트가 안전해요: setCount(prev => prev + 1).

실제로 어디에 쓰나
  • 입력값 관리 — 검색어, 폼 입력칸의 글자: const [q, setQ] = useState("")
  • 열림/닫힘 — 모달·상세 Sheet·드롭다운이 열렸는지: const [open, setOpen] = useState(false)
  • 선택 상태 — 표에서 선택한 행, 활성 탭 인덱스, 체크한 항목
  • 로딩/토글 — 버튼 로딩 표시, 접기/펴기 같은 on/off

2.2 useEffect — "렌더 끝난 뒤 실행할 부수 효과"

부수 효과? 란, 컴포넌트가 화면 모양을 계산하는 일과 별개로 바깥 세상과 주고받는 일입니다 — 서버 요청, 타이머, 이벤트 구독, 브라우저 제목 변경 등. 이런 일은 useEffect 안에서 처리해요.

의존성 배열 — 언제 실행될지 정하는 목록

코드실행 시점
useEffect(fn, [])처음 화면에 나타날 때 딱 1번
useEffect(fn, [a, b])a 또는 b 가 바뀔 때마다
useEffect(fn)매 렌더마다 (거의 안 씀)
가장 많이 틀리는 부분

이펙트 함수 안에서 읽는 값(state·props·함수)은 전부 의존성 배열? 에 넣으세요. 빼먹으면 옛날 값을 붙잡는? 버그가 생깁니다.

아래는 count 가 바뀔 때마다 브라우저 제목(탭 이름)을 바꾸는 예제입니다. 버튼을 누르고 브라우저 탭 제목이 바뀌는지 보세요.

실제로 어디에 쓰나
  • 화면 진입 시 데이터 로딩 — 페이지가 뜨면 목록/상세 가져오기 (실무에선 보통 useQuery가 대신함 → 4장)
  • 주소(URL) 변화 반응useParams로 받은 projectId가 바뀌면 해당 상세 다시 불러오기
  • 다른 값에 맞춰 동기화 — 대분류가 바뀌면 중분류 선택값 초기화
  • 브라우저 API 연동 — 문서 제목 변경, localStorage 저장, resize/scroll 구독

2.3 정리 함수(cleanup) — 뒷정리를 잊지 마세요

useEffect 안에서 함수를 return 하면 그게 정리 함수? 입니다. 다시 실행되기 직전컴포넌트가 사라질 때 호출돼요. 타이머·구독을 만들었다면 반드시 정리해야 메모리 누수와 중복 실행을 막습니다.

부수 효과가 "수도꼭지를 트는 것"이라면, 정리 함수는 "잠그는 것". 틀었으면 잠가야 합니다.

실험: 아래 타이머에서 return () => clearInterval(id) 줄을 지우고 실행한 뒤, 다시 초기화해보세요. 정리가 왜 필요한지 감이 옵니다.

정리가 꼭 필요한 경우
  • 타이머setInterval/setTimeoutclearInterval로 해제
  • 이벤트 구독addEventListenerremoveEventListener
  • 진행 중 요청 무시 — 화면을 빠르게 옮길 때 이전 fetch 결과를 버리는 ignore 플래그

2.4 useRef — "DOM 가리키기" + "숨은 값 보관"

useRef 는 두 가지 용도가 있어요.

① DOM 요소를 직접 가리키기

② 리렌더를 일으키지 않는 값 저장

state 는 바꾸면 리렌더되지만, ref.current 는 바꿔도 리렌더가 안 됩니다. 화면에 안 보여도 되는 값(타이머 id 등) 보관에 좋아요.

useStateuseRef
값 바꾸면 리렌더?✅ 일어남❌ 안 일어남
주 용도화면에 보이는 값DOM 접근 / 숨은 값

→ 이 차이를 직접 눌러 확인: 개념 시각화 1번 (useState vs useRef)

실제로 어디에 쓰나
  • 자동 포커스 — 검색창/모달이 열리면 입력칸에 커서 두기
  • 숨은 파일 input 열기 — 커스텀 버튼 클릭 시 fileRef.current.click()
  • 스크롤 제어 — 특정 영역으로 scrollIntoView()
  • 화면과 무관한 값 보관 — 타이머 id, 이전 값(previous value) 저장

2.5 useMemo — "비싼 계산 결과 기억하기"

매 렌더마다 다시 하기 아까운 무거운 계산캐싱? 합니다. 의존성이 바뀔 때만 다시 계산해요.

남발 금지

가벼운 계산에 useMemo 를 쓰면 오히려 느려져요(기억하는 비용도 듦). 정말 무거울 때만 씁니다. 실무의 동적 표 컬럼 예제는 6장에서 다룹니다.

실제로 어디에 쓰나
  • 동적 표 컬럼 만들기 — 검수 항목 수에 따라 표 컬럼을 계산 (주문 화면, 6장)
  • 큰 목록 가공 — 수백 행을 필터/정렬/그룹핑한 결과 재사용
  • 무거운 계산 — 주문 금액 합계 같은 수식 계산 결과
  • 옵션 가공 — 공통코드 목록을 셀렉트박스용 형태로 변환

2.6 useCallback — "함수 기억하기"

useMemo을 기억한다면 useCallback함수를 기억합니다. 컴포넌트가 리렌더되면 안에서 만든 함수도 매번 새 함수가 되는데, 그 함수를 memo 된 자식이나 다른 훅의 의존성으로 넘길 때 불필요한 작업을 막아줘요.

const onSearch = useCallback((values) => {
  fetchList(values)
}, [])  // 의존성이 없으면 함수는 한 번만 만들어짐
한 줄 정리

useMemo = 값 캐싱, useCallback = 함수 캐싱. 둘 다 꼭 필요할 때만 쓰세요.

실제로 어디에 쓰나
  • 자식에 넘기는 핸들러 — 검색 함수를 SearchForm에 전달해 불필요한 리렌더 방지
  • 표 셀 콜백memo된 행/셀에 onChange를 안정적으로 전달
  • 훅 의존성 안정화useEffect 의존성으로 쓰는 함수가 매번 바뀌지 않게

2.7 useContext — "props 없이 값 공유하기"

값을 자식 → 자식 → 자식으로 props? 로 계속 넘기기 번거로울 때 씁니다. createContext 로 통로를 만들고, Provider 로 값을 흘려보낸 뒤, useContext 로 꺼내요.

Context 는 와이파이. Provider 가 공유기, useContext 가 신호를 잡는 기기. 중간 컴포넌트를 거치지 않고 바로 받습니다.
실제로 어디에 쓰나
  • 로그인 사용자·권한 — 어디서든 useAuth()로 현재 사용자/역할 읽기 (내부적으로 공유 상태 사용)
  • 테마 — 다크/라이트 모드 값을 앱 전체에서 공유
  • 폼 컨텍스트FormProvider 아래 깊은 입력 컴포넌트가 폼 상태 공유 (react-hook-form)
  • 복잡한 화면 상태 — 프로젝트 상세처럼 여러 탭이 한 화면 상태를 공유 (실무에선 Zustand도 같은 목적)

2.8 그 외 내장 훅 — useId · useImperativeHandle

자주는 아니지만 이 프로젝트에도 등장하는 내장 훅들입니다. "이런 게 있다" 정도만 알아두세요.

useId? — 겹치지 않는 고유 id 만들기

입력창과 라벨을 연결할 때처럼 화면에 여러 번 나와도 겹치지 않는 id 가 필요할 때 씁니다.

useImperativeHandle? — 자식의 기능을 부모에게 노출(고급)

보통은 부모 → 자식으로 props를 내려보내지만, 가끔 부모가 자식의 특정 동작(예: focus())을 직접 호출해야 할 때가 있어요. 그럴 때 ref 와 함께 노출할 함수만 골라 줍니다. 고급/예외적 훅이라 대부분은 props/state로 해결하는 게 낫습니다.

// 자식: 부모에게 focus 기능만 노출
const FancyInput = forwardRef((props, ref) => {
  const inputRef = useRef(null)
  useImperativeHandle(ref, () => ({
    focus: () => inputRef.current.focus(),
  }))
  return <input ref={inputRef} />
})

// 부모: ref.current.focus() 로 자식 동작 호출
const ref = useRef(null)
<FancyInput ref={ref} />
<button onClick={() => ref.current.focus()}>포커스</button>
useReducer? 는?

복잡한 state를 action으로 정리하는 내장 훅이지만, 이 프로젝트는 대부분 Zustand? store로 복잡한 상태를 다뤄서 직접 쓰는 일은 드뭅니다. (useLayoutEffect, useTransition 등 다른 내장 훅도 있지만 이 프로젝트에선 거의 안 써요.)

한눈에 정리

한 줄 요약언제 쓰나
useState바뀌면 화면도 바뀌는 값거의 모든 컴포넌트
useEffect렌더 뒤 부수 효과 + 정리요청, 타이머, 구독
useRef숨은 값 보관 / DOM 접근포커스, 타이머 id
useMemo무거운 캐싱비싼 계산
useCallback함수 캐싱자식에 넘기는 함수
useContextprops 없이 값 공유테마, 로그인 정보