2025. 11. 28. 08:41ㆍReact/Basic
이 글에서는 React의 Hook의 개요와 등장 배경, 동작 원리, Hook Rule 등에 대해 학습한 내용을 정리하고 공유합니다.
- Hook
- 동작 원리
- Hook Rule
- 렌더링 프로세스와 Hook 실행 시점
- 회고
- 정리
Hook
함수형 컴포넌트가 상태 및 기타 React 기능을 사용할 수 있도록 하는 함수
ex) useState, useEffect, useCallback, useMemo 등
Hook을 통해 클래스형 컴포넌트처럼 상태 관리 및 기타 React 기능을 사용할 수 있습니다.
use로 시작하는 JS 함수이지만, 정해진 규칙을 따르지 않을 경우 React가 올바르게 동작하지 않을 수 있습니다.
등장 배경
Hook은 React 16.8에 추가된 기능으로, 이전에는 컴포넌트의 상태 관리, 라이프 사이클 제어 등을 수행하기 위해서는 클래스형 컴포넌트를 사용했습니다.
하지만 클래스형 컴포넌트는 아래와 같은 문제가 있습니다.
- 로직 재사용이 어렵다
- 상태 관리, 생명 주기 제어시 클래스형 컴포넌트 사용이 강제된다.
- 여러 생명주기 메서드로 분산된 로직
- this 바인딩
Hook 도입을 통해 함수형 컴포넌트 사용시 아래의 이점이 있습니다.
- 컴포넌트 단위의 로직 재사용
- UI와 비즈니스 로직 분리
- 함수형 컴포넌트가 UI 구성 외, 상태 관리 및 부수 효과 처리
Custom Hook
Hook은 상태 관리 도구이자, 연관된 로직을 하나로 묶는 단위이기도 합니다.
연관된 로직을 여러 컴포넌트에서 사용하고 싶을 때 커스텀 훅을 만들어 사용할 수 있습니다.
동작 원리
함수는 기본적으로 상태를 저장하지 못합니다.
따라서 React Hook은 컴포넌트 함수 실행 시 “호출 순서(인덱스)”를 기준으로 React 내부 메모리에 상태·효과를 저장하고, 리렌더 때 동일 순서로 다시 꺼내 사용합니다.
상태는 컴포넌트 외부에 저장되며, 클로저 원리를 이용해 관리됩니다.
따라서 컴포넌트가 리랜더링 되어도 변경된 값을 가져올 수 있으며, 컴포넌트가 Unmount될 때, 외부 상태도 초기화 됩니다.
Hook 호출 순서에 따른 상태 관리 예시
호출 순서에 따라 상태를 관리하므로, Hook의 호출 순서가 변동될 수 있는 방식은 지양해야 합니다.
function collect() {
const [name, setName] = useState('React');
const [category, setCategory] = useState('Hook');
useEffect(() => {
setName('Reacttttt');
}, [category]);
return <>...</>
}
생명 주기에 따라 내부 메모리에 상태를 저장/관리하는 과정을 살펴보겠습니다.
작업 메모리 변화
| Mount시, 빈 메모리 배열 할당 | [] |
| useState('React') 초기화, 값 저장 | ['React’] |
| useState('Hook') 초기화, 값 저장 | ['React’, ‘Hook’] |
| useEffect 초기화, 함수 정보 저장 | ['React’, ‘Hook’, <useEffect>] |
| setCategory(’Hoooook!') 실행, Update 발생 및 값 갱신 | ['React’, ‘Hoooook!’, <useEffect>] |
| 리렌더링 후 각 Hook의 기본 동작 처리 | 변화없음 |
| useState('React') 배열 0번 값(’React’) 할당 | 변화없음 |
| useState('Hook') 배열 1번 값(’Hoooook!’) 할당 | 변화없음 |
| useEffect 의 의존성 배열에 의해 명령 실행 및 2차 렌더링: setName('Reaaaaaact') | ['Reaaaaaact’, ‘Hoooook!’, <useEffect>] |
| Unmount | 메모리 해제 |
Hook Rule
React가 Hook을 올바르게 인식하도록 하기 위해서는 함수 최상위에서만 선언 및 호출할 것을 지향합니다.
function BadComponenet() {
const [name, setName] = useState("React");
if ( name == "React" ) [
useEffect(() => {
setName("Not React");
});
}
const [temp,setTemp] = useState(0); // Hook의 호출 시점이 변동된다.
}
만약 Hook 순서의 선언 및 호출 시점을 준수하지 않을 경우, 잘못된 데이터를 꺼내오며 오류가 발생할 수 있습니다. React는 이를 방지하고자 “Rendered fewer hooks than expected." 라는 오류를 발생시킵니다.
ESLint에는 “Rules of Hooks”라는 검사기가 기본 탑재되어 있어 작성 규칙을 어길 시 경고를 표시합니다.
Hook 규칙을 위반하는 상황 및 예시
Hook 규칙을 위반하는 선언 상황은 크게 아래와 같이 구분됩니다.
- 조건/루프 내부에 선언한 경우
- 컨텍스트를 찾지 못하는 경우
- 클래스/모듈 레벨로 선언된 경우
아래는 선언 시점이 Hook 규칙을 위반하는 상황의 예시코드입니다.
https://react.dev/reference/eslint-plugin-react-hooks/lints/rules-of-hooks
rules-of-hooks – React
The library for web and native user interfaces
react.dev
// ❌ 조건식 또는 반복문 내부에 선언된 경우
if (isLoggedIn) {
const [user, setUser] = useState(null);
}
// ❌ Hook선언보다 컴포넌트가 조기 반환 되는 경우
if (!data) return <Loading />;
const [processed, setProcessed] = useState(data);
// ❌ 콜백/이벤트 핸들러 내부
<button onClick={() => {
const [clicked, setClicked] = useState(false);
}}/>
// ❌ 비동기 함수 / try-catch / 클래스 메서드 내부
try {
const data = use(promise);
} catch (e) {
// error handling
}
// ❌ 모듈 수준으로 선언된 경우
const globalState = useState(0); // Outside component
렌더링 프로세스와 Hook 실행 시점
React는 컴포넌트를 다시 실행할 때, **화면을 그리는 과정(Render Phase)과 실제 DOM에 반영하는 과정(Commit Phase)**으로 나뉩니다.
1. 계산 단계(Render Phase)
React가 컴포넌트 함수를 호출해 “어떤 UI를 그릴지” 계산하는 단계입니다.
아래 작업들이 수행되며, 빠른 UI 생성을 위해 DOM 수정·네트워크 요청과 같은 사이드 이펙트가 없도록 설계해야 합니다.
- 컴포넌트 함수 실행
- useState의 현재 값 반환 / 초기값 평가
- useMemo의 메모이제이션 계산
- JSX → React element 생성
2. 반영 단계(Commit Phase)
Render 단계에서 계산된 UI를 실제 DOM에 반영하는 단계입니다.
아래 작업들이 수행되며, 첫 화면 표시 후 비동기 통신, DOM 조작과 같이 무거운 사이드 이펙트를 수행합니다.
- DOM 업데이트 및 변경 사항 반영
- 화면(paint) 발생
- useLayoutEffect 실행
- paint 이후 useEffect 실행
useEffect의 비동기 통신으로 실제 데이터를 응답받은 경우, 효율적인 추가 렌더링으로 데이터를 표시해 사용자 경험을 향상 시킵니다.
useEffect의 Stale Closur(오래된 클로저)문제
Hook은 클로저이므로, useEffect 안에서 setInterval 또는 이벤트 리스너 등록 시, 상태 값이 업데이트 되지 않고 초기값(옛날값)으로 고정되어있는 현상이 발생할 수 있습니다.
이를 해결하기 위해서는 올바른 의존성 배열 구성 또는 함수형 업데이트를 사용하는 방법이 있습니다.
정리
- Hook은 함수형 컴포넌트가 상태 및 기타 React 기능을 사용할 수 있도록 하는 함수
- 클로저와 실행/호출 순서에 의존해 상태를 관리하므로, 올바르게 사용해야 한다.
- 로직의 재사용성, 가독성 높여준다.
- 연관된 로직을 커스텀 훅으로 만들어 여러 컴포넌트에서 사용해 볼 수 있다.
회고
이번 학습을 통해 Hook을 단순히 “React가 기본 제공하는 상태 관리 도구”로 이해했던 기존 관점에서 벗어나, 클래스 컴포넌트에서만 가능하던 기능을 함수형 컴포넌트에서도 사용할 수 있도록 만든 핵심 메커니즘으로 인식하게 되었다. 특히 Hook이 단순 API가 아니라, 클로저·호출 순서·렌더/커밋 단계 위에서 동작하는 구조적 시스템이라는 점이 가장 인상 깊었다.
폼을 만들 때 여러 개의 useState를 나열하는 방식이 자연스럽다고 생각했는데, 이번 학습을 계기로 로직 단위로 캡슐화한 커스텀 훅을 만들어 재사용하는 방식이 더 적절하다는 점도 깨달았다. 앞으로는 처음부터 상태를 컴포넌트 안에 흩뿌리기보다 “이 로직을 훅으로 분리할 수 있을까?”를 먼저 고민해 볼 것 같다.
또한 useEffect를 단순히 “어떤 행동을 넣는 곳”으로 쓰던 습관도 돌아보게 되었다. 렌더 단계는 순수해야 하고, 실제 부수 효과는 커밋 이후에 실행된다는 구조를 이해한 만큼, 앞으로는 정말 effect가 필요한지, 의존성은 정확한지를 더 신중하게 판단하려 한다. 필요할 때만 쓰고, 불필요한 effect는 줄이는 방향이 효율성과 유지보수성 모두에 도움이 된다는 걸 깨달았다.
마지막으로, Hook을 배우면서 “React는 결국 상태와 로직 조립을 위한 기반을 제공하는 라이브러리”라는 점을 다시 한 번 느꼈다. 각 Hook이 따로 노는 API가 아니라, UI·상태·부수 효과를 조립해 하나의 구조를 만드는 빌딩 블록이라는 점을 늦게나마 알게 되었고, 앞으로 작성할 컴포넌트는 이러한 구조적 관점을 더 많이 담게 될 것 같다.
'React > Basic' 카테고리의 다른 글
| [React] LifeCycle (0) | 2025.11.27 |
|---|---|
| [React] State (0) | 2025.11.27 |
| [React] Props (0) | 2025.11.27 |
| [React] Component (0) | 2025.11.24 |