리액트를 사용하게 되는 큰 의미 중 하나인 React Hook.
도대체 뭐길래 꼭 사용해야하는지, 어떻게 쓰는지 알아보도록 하겠다
Hook이란?
리액트가 컴포넌트를 다시 실행해도 기억하는 기능을 가진 특별한 함수
리액트의 컴포넌트는 사실 함수라서 실행하면 변수들이 다 사라져야 해요.
useState같은 Hook은 그걸 기억하게 해준다!
일반함수
let count = 0
function MyComponent() {
count = count + 1
console.log(count)
}
// => 일반 함수는 매 실행마다 다시 시작해야 하기 때문에 UI 상태 기억 불가
리액트 Hook
const [count, setCount] = useState(0)
// 리렌더될 때도 count가 초기화되지 않고 남아있다!
자주쓰는 React Hook 정리
React에서 가장 많이 쓰이는 Hook & 특징 정리useState — 상태 관리의 시작점React에서 가장 기본이 되는 Hook.컴포넌트가 가지고 있어야 하는 “변하는 값”을 저장.렌더링에 영향을 줌.setState가 호출
tin814.tistory.com
간단한 Hook의 예시
useCounter Hook
import { useState } from 'react';
export const useCounter = (initialValue: number = 0) => {
const [count, setCount] = useState(initialValue);
const increment = () => setCount(prev => prev + 1); // +1하기
const decrement = () => setCount(prev => prev - 1); // -1하기
const reset = () => setCount(initialValue); // initialValue로 초기화
return { count, increment, decrement, reset };
};
사용법:
function MyComponent() {
const { count, increment, decrement, reset } = useCounter(10);
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>+</button> // increment
<button onClick={decrement}>-</button> // decrement
<button onClick={reset}>Reset</button> // reset
</div>
);
}
// 다양한 로직들을 사용할 수 있도록 로직을 hook으로 묶음
useState+ useEffect를 사용한 CustomHook 생성 ; useLocalStorage
import { useState, useEffect } from 'react';
export const useLocalStorage = <T,>(key: string, initialValue: T) => {
// localStorage에서 초기값 가져오기
const [value, setValue] = useState<T>(() => {
const stored = localStorage.getItem(key);
return stored ? JSON.parse(stored) : initialValue;
});
// 값이 변경될 때마다 localStorage 업데이트
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue] as const;
};
사용법:
function MyComponent() {
const [name, setName] = useLocalStorage('userName', '');
return (
<input
value={name}
onChange={(e) => setName(e.target.value)}
/>
);
}
커스텀 훅 만드는 단계별 가이드
Step 1: 파일 생성
# hooks 폴더에 생성
src/hooks/useCounter.ts
Step 2: 간단한 예시 - useCounter
import { useState } from 'react';
export const useCounter = (initialValue: number = 0) => {
const [count, setCount] = useState(initialValue);
const increment = () => setCount(prev => prev + 1);
const decrement = () => setCount(prev => prev - 1);
const reset = () => setCount(initialValue);
return { count, increment, decrement, reset };
};
사용법:
function MyComponent() {
const { count, increment, decrement, reset } = useCounter(10);
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
<button onClick={reset}>Reset</button>
</div>
);
}
커스텀 훅 작성 규칙
해야 할 것:
- 항상 "use"로 시작하는 이름 사용
- 다른 훅들을 조합하여 사용 가능
- 재사용 가능한 로직 추출
- 필요한 값과 함수만 반환
하지 말아야 할 것:
- 조건문 안에서 훅 호출
- 반복문 안에서 훅 호출
- 일반 함수에서 훅 호출
왜 커스텀 훅을 조건문, 반복문, 일반 함수 안에서 훅을 호출하면 안되는 걸까?
결론
React는 컴포넌트가 렌더링될 때 Hook이 ‘항상 같은 순서로 호출된다’는 것을 전제로 내부 구조를 짜놨다.
그리고 만약
- 조건문 안에서 호출 → 실행될 때도 있고 안 될 때도 있음
- 반복문 안에서 호출 → 실행 횟수가 달라짐
- 일반 함수 안에서 호출 → 호출 시점이 일정하지 않음
이렇게 되면 Hook이 “순서 기반”으로 움직이는 React 내부 시스템이 완전히 꼬여버림.
React가 Hook을 어떻게 관리하길래?
React는 컴포넌트를 렌더링할 때
각 Hook의 값을 순서대로 배열에 저장해두고 사용
예시로 컴포넌트가 있다고 해보자:
function Component() {
const a = useState(1); // Hook #1
const b = useState(2); // Hook #2
const c = useEffect(() => {}, []); // Hook #3
}
React 내부에서는 대략 이렇게 저장함:
Hook index 0 -> useState(1)
Hook index 1 -> useState(2)
Hook index 2 -> useEffect(...)
그리고 다음 렌더링 때도 같은 순서라고 가정하고 재사용함.
문제 상황 발생 예시
조건문 안에서 훅 호출하면?
if (props.visible) {
const [a, setA] = useState(0); // ❌ 조건문 안
}
const [b, setB] = useState(0);
첫 번째 렌더
visible = true
Hook #1: a
Hook #2: b
두 번째 렌더
visible = false
Hook #1: b ← ⚠ 순서가 달라짐!
React는 “Hook #1은 useState가 되어야 한다”,
근데 이번 렌더링에서는 첫 번째 useState가 호출되지 않았음.
=>그래서 상태가 완전히 뒤바뀌거나, 에러가 터짐.
반복문 안에서 훅 호출
for (let i = 0; i < items.length; i++) {
const [value, setValue] = useState(0); // ❌ 반복문
}
첫 번째 렌더
items.length = 3 → Hook 3개 생성
두 번째 렌더
items.length = 1 → Hook 1개만 생성
➡ Hook 개수 자체가 달라짐 → React 내부 인덱스 꼬임
➡ 상태, memoized 값, effect 순서가 전부 잘못 매칭되어 버림
일반 함수 안에서 Hook 호출
function doSomething() {
const [a, setA] = useState(0); // ❌ 함수를 호출할 때마다 실행됨
}
function Component() {
doSomething();
const [b, setB] = useState(0);
}
문제:
- doSomething()이 언제 호출될지 React는 모름
- 렌더링마다 호출될 수도, 안 될 수도 있음
- React의 “Hook 순서 체계”가 보장되지 않음
➡ 결국 React 내부 Hook 배열 인덱스가 예측 불가능해짐.
그래서 나온 규칙:
Hooks must be called unconditionally at the top level
이 규칙을 지키면 React는 다음을 보장할 수 있다:
- Hook의 순서가 항상 같음
- 상태가 다른 Hook과 섞이지 않음
- 메모이제이션이 정확한 Hook에 연결됨
- cleanup이 올바른 effect와 매칭됨
커스텀 훅을 만드는 핵심 규칙
커스텀 훅은 Hook을 감싸서 로직을 재사용하는 도구.
그래서 커스텀 훅도:
컴포넌트 최상단에서만 호출 가능
조건/반복/일반 함수 내부에서는 호출 불가
❗ 커스텀 훅 내부에서는 자유롭게 Hook 사용 가능
→ 단, 그 커스텀 훅 자체는 “항상 동일한 위치에서 호출”되어야 함.
쉽게 비유하면…
React는 Hook을 "줄 서서 기다리는 사람들"로 관리함.
- 첫 번째 사람: useState(카운터)
- 두 번째 사람: useState(토글)
- 세 번째 사람: useEffect(데이터 fetch)
근데 어떤 렌더링에서 갑자기 첫 번째 사람이 없거나,
2명만 있다가 다음 렌더링은 5명이 오면?
“얘가 아까 그 사람인가..?”
“useState였나 useEffect였나…?”
React는 혼란에 빠짐.
그래서 "줄(호출 순서)을 항상 일정하게 유지"해야 함.
'STUDY > [ React ]' 카테고리의 다른 글
| 왜 리액트는 Hook을 순서 기반으로 설계했는가? (0) | 2025.11.27 |
|---|---|
| 리액트 기능 만들기(추후추가) (0) | 2025.11.27 |
| 리앧ㄱ트 컴포넌트만들기(추후추가) (0) | 2025.11.26 |
| useState 와 useContext 차이점 및 context를 사용해야 하는 이유 (0) | 2025.11.26 |
| 자주쓰는 React Hook 정리 (0) | 2025.11.26 |