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 |
useRef | DOM 접근 / 숨은 값 | 이 장 2.4 |
useMemo | 값 캐싱 | 이 장 2.5 |
useCallback | 함수 캐싱 | 이 장 2.6 |
useContext | props 없이 값 공유 | 이 장 2.7 |
| useId? | 고유 id 생성 | 이 장 2.8 |
| useImperativeHandle? | 자식 기능 노출(고급) | 이 장 2.8 |
| useReducer? | 복잡한 state(이 프로젝트선 드묾) | 2.8에서 언급 |
② 라이브러리 훅 (설치한 패키지가 제공)
| 훅 | 출처 | 쓰임 | 배우는 곳 |
|---|---|---|---|
useQuery · useMutation · useQueryClient | React Query? | 서버 데이터 조회/변경/캐시 | 4장 |
useStore (create) | Zustand? | 전역 상태 | 4장 |
useForm · useWatch · useFieldArray · useFormContext | react-hook-form? | 폼 값·검증·제출 | 5장 |
useNavigate · useParams | TanStack Router? | 화면 이동 / 주소 값 | 5장 |
useSignal | Preact Signals | 세밀한 상태(필터·정렬 등) | 4장에서 언급 |
useSensor · useSortable | dnd-kit | 드래그&드롭 | 참고용 |
useDropzone | react-dropzone | 파일 업로드 | 참고용 |
useSidebar · useFormField | Shadcn UI | UI 컴포넌트 내부 | 7장 |
③ 커스텀 훅 (이 프로젝트가 직접 만든 것 — 약 195개)
커스텀 훅? 은 use 로 시작하는 내가 만든 함수예요.
반복되는 훅 로직을 모아 재사용합니다. 이 프로젝트에서 가장 많이 쓰는 종류:
| 예시 | 역할(이름 패턴) |
|---|---|
useAuth (46회) | 로그인 사용자 정보·권한 꺼내기 |
useSearchForm (23회) | 검색 폼 상태 관리 공통 로직 |
useEquipmentPageStore, useUserPageStore … | use…PageStore : 페이지별 화면 상태 store |
useUserListQuery, useTestReqQuery … | use…Query : 특정 데이터 조회 useQuery 묶음 |
useProjectAction, useRecipeAction … | use…Action : 저장·삭제 등 동작 모음 |
훅 = 내장 훅(이 장) + 라이브러리 훅(4·5·7장) + 커스텀 훅(3장에서 만드는 법). 이 장의 6개는 나머지 모든 훅의 토대라서 가장 먼저, 가장 탄탄히 익히는 거예요.
2.1 useState — "바뀌면 화면도 바뀌는 변수"
일반 변수 let count = 0 은 값을 바꿔도 화면이 그대로입니다.
useState 로 만든 값은 바꾸면 React가 자동으로 화면을 다시 그립니다(리렌더?).
useState 는 화이트보드에 적힌 숫자. setCount 로 고치면 보드가 자동으로 새로 그려집니다.아래 코드에서 초기값 0 을 10 으로 바꾸고 ▶ 실행 해보세요.
값을 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/setTimeout→clearInterval로 해제 - 이벤트 구독 —
addEventListener→removeEventListener - 진행 중 요청 무시 — 화면을 빠르게 옮길 때 이전 fetch 결과를 버리는
ignore플래그
2.4 useRef — "DOM 가리키기" + "숨은 값 보관"
useRef 는 두 가지 용도가 있어요.
① DOM 요소를 직접 가리키기
② 리렌더를 일으키지 않는 값 저장
state 는 바꾸면 리렌더되지만, ref.current 는 바꿔도 리렌더가 안 됩니다.
화면에 안 보여도 되는 값(타이머 id 등) 보관에 좋아요.
| useState | useRef | |
|---|---|---|
| 값 바꾸면 리렌더? | ✅ 일어남 | ❌ 안 일어남 |
| 주 용도 | 화면에 보이는 값 | 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 로 꺼내요.
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>복잡한 state를 action으로 정리하는 내장 훅이지만, 이 프로젝트는 대부분
Zustand? store로 복잡한 상태를 다뤄서 직접 쓰는 일은 드뭅니다.
(useLayoutEffect, useTransition 등 다른 내장 훅도 있지만 이 프로젝트에선 거의 안 써요.)
한눈에 정리
| 훅 | 한 줄 요약 | 언제 쓰나 |
|---|---|---|
useState | 바뀌면 화면도 바뀌는 값 | 거의 모든 컴포넌트 |
useEffect | 렌더 뒤 부수 효과 + 정리 | 요청, 타이머, 구독 |
useRef | 숨은 값 보관 / DOM 접근 | 포커스, 타이머 id |
useMemo | 무거운 값 캐싱 | 비싼 계산 |
useCallback | 함수 캐싱 | 자식에 넘기는 함수 |
useContext | props 없이 값 공유 | 테마, 로그인 정보 |