리액트를 처음 배우면서 "리액트 훅"이라는 용어를 자주 접하게 되었다.
더불어, Zustand를 공부할 때도 "훅"을 사용해야 한다는 이야기를 들으니 ..
이 개념에 대해 좀 더 깊이 공부할 필요성을 느꼈다.
그래서 이번 글에서는 리액트 훅, 사용자 정의 훅, 훅 작성 규칙 등을 자세히 살펴보려고 한다.
1. 리액트 훅(Hook)이란?
리액트에서 컴포넌트는 화면을 구성하는 단위로,
상태(state)를 가지고 있으며 이 상태가 바뀔 때마다 화면도 자동으로 업데이트된다.
리액트 훅은 함수형 컴포넌트에서 (1) 상태를 관리하고, (2) 부수 효과를 처리할 수 있도록 도와주는 특별한 함수이다.
클래스형 컴포넌트를 사용하지 않고도 상태를 관리하고, API 호출과 같은 작업을 처리할 수 있다.
리액트에서 자주 사용하는 훅은 useState, useEffect, useContext 등이 있다.
2. 리액트에서 가장 많이 사용되는 훅들
2-1. useState : 상태 관리
- 컴포넌트 내에서 상태를 관리할 수 있게 해주는 훅
ex) 버튼 클릭 시 카운트가 증가하는 기능
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0); // 상태 변수와 그 값을 변경하는 함수
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
- useState(0)에서 0은 초기값이다.
- count는 현재 상태를 나타내고, setCount는 상태를 변경하는 함수이다.
2-2. useEffect : 부수 효과 처리
- 부수 효과를 처리할 때 사용
부수 효과란, 함수나 컴포넌트 내에서 발생하는 주된 동작 외의 추가적인 동작을 의미(ex API 호출, 타이머 설정 등)
ex) 데이터 요청이나 이벤트 리스너 추가 등 렌더링 외에 필요한 작업을 할 때 사용
import React, { useState, useEffect } from 'react';
function Timer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setSeconds(prev => prev + 1);
}, 1000);
return () => clearInterval(timer); // 타이머 정리
}, []); // 빈 배열: 첫 렌더링 때만 실행
return <p>Timer: {seconds} seconds</p>;
}
- useEffect는 컴포넌트가 처음 렌더링될 때 타이머를 시작한다.
- 컴포넌트가 사라질 때 타이머를 정리하는 작업도 return문에서 처리한다.
2-3. useContext : 전역 상태 관리
- 여러 컴포넌트에서 공통된 데이터를 관리하고 공유하는 데 유용
import React, { createContext, useContext, useState } from 'react';
const CountContext = createContext(); // 전역 상태를 위한 Context
function CountProvider({ children }) {
const [count, setCount] = useState(0);
return (
<CountContext.Provider value={{ count, setCount }}>
{children}
</CountContext.Provider>
);
}
function Display() {
const { count } = useContext(CountContext); // Context에서 상태 가져오기
return <p>Current Count: {count}</p>;
}
function Increment() {
const { setCount } = useContext(CountContext); // 상태 변경 함수 사용
return <button onClick={() => setCount(prev => prev + 1)}>Increment</button>;
}
function App() {
return (
<CountProvider>
<Display />
<Increment />
</CountProvider>
);
}
- CountContext를 통해 상태를 공유하며, useContext로 쉽게 접근할 수 있다.
3. Zustand와 리액트 훅
- Zustand는 리액트에서 전역 상태를 관리하는 라이브러리
- 리액트의 useState 훅을 사용하면 컴포넌트 내부에서만 상태를 관리할 수 있지만, Zustand는 상태를 여러 컴포넌트에서 공유할 수 있게 해 준다.
import create from 'zustand';
const useStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}));
function Counter() {
const { count, increment } = useStore(); // Zustand 훅 사용
return (
<div>
<p>{count}</p>
<button onClick={increment}>Increment</button>
</div>
);
}
- useStore 훅을 사용해 전역 상태를 관리하고, 상태를 다른 컴포넌트에서 쉽게 공유할 수 있다.
4. 사용자 정의 훅(Custom Hook)
- 리액트 훅을 활용하여 공통된 로직을 재사용할 수 있다.
이것을 사용자 정의 훅이라 하며, 여러 컴포넌트에서 필요한 로직을 공유할 때 유용하다.
ex) useWindowWidth : 화면의 너비를 추적하는 로직을 재사용 가능한 훅
import { useState, useEffect } from 'react';
function useWindowWidth() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth);
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return width;
}
function App() {
const width = useWindowWidth(); // 사용자 정의 훅 사용
return <h1>Window width: {width}px</h1>;
}
5. 리액트 훅 작성 규칙 (Rules of Hooks)
리액트 훅을 사용할 때는 몇 가지 중요한 규칙을 반드시 지켜야 한다.
이 규칙들을 지키지 않으면 훅이 예상대로 작동하지 않거나 오류가 발생할 수 있다.
1. 컴포넌트 내부에서만 호출
-> 훅은 함수형 컴포넌트 안에서만 호출해야 한다.
- 리액트 훅은 함수형 컴포넌트의 최상위 레벨에서 호출되어야 한다.
- 클래스형 컴포넌트나 일반 자바스크립트 함수 안에서는 훅을 사용할 수 없다.
- 훅을 사용하려면 반드시 리액트의 함수형 컴포넌트 내부에서 호출해야 한다.
잘못된 예시
function notAComponent() {
const [state, setState] = useState(0); // 오류 발생 !! 함수형 컴포넌트 외부에서 훅을 사용했기 때문에 오류가 발생한다.
}
올바른 예시
function MyComponent() {
const [state, setState] = useState(0); // 컴포넌트 내부에서 훅을 호출해야 한다.
return <div>{state}</div>;
}
2. 최상위에서만 호출
-> 조건문, 반복문, 또는 중첩 함수 안에서 훅을 호출하지 말고, 컴포넌트의 최상위 레벨에서만 호출해야 한다.
- 리액트 훅은 반복문, 조건문, 중첩된 함수 안에서 호출해서는 안 된다.
- 훅은 렌더링 되는 컴포넌트의 매 렌더링마다 동일한 순서로 호출되어야 하므로, 조건에 따라 훅을 호출하면 렌더링 간에 훅 호출 순서가 달라져서 오류가 발생할 수 있다.
잘못된 예시
function Counter({ count }) {
if (count > 0) {
const [state, setState] = useState(0); // 조건문 안에서 훅을 호출하면 안 된다.
}
return <div>{count}</div>;
}
올바른 예시
function Counter({ count }) {
const [state, setState] = useState(0); // 최상위에서 호출해야 한다.
if (count > 0) {
// 조건문 안에서 상태를 사용할 수 있다.
}
return <div>{count}</div>;
}
3. use로 시작
-> 리액트 훅은 모두 use로 시작해야 한다.
- 리액트에서 제공하는 내장 훅과 사용자 정의 훅은 모두 use로 시작해야 한다.
- 이것은 훅이 리액트의 특별한 함수임을 구분할 수 있게 해주는 규칙이다.
- ex) useState, useEffect, useContext 등은 리액트에서 제공하는 훅
- 사용자가 정의한 훅도 use로 시작해야 하며, 이를 통해 리액트가 해당 함수가 훅임을 인식하고 올바르게 처리할 수 있다.
잘못된 예시
function myCustomHook() { // 사용자 정의 훅 이름은 'use'로 시작해야 한다.
const [state, setState] = useState(0);
return [state, setState];
}
올바른 예시
function useCustomHook() { // 사용자 정의 훅 이름은 'use'로 시작해야 한다.
const [state, setState] = useState(0);
return [state, setState];
}
훅 규칙을 지켜야 하는 이유
이 규칙들은 리액트의 렌더링 사이클에 맞추어 훅이 일관되게 호출되도록 보장한다.
예를 들어, 훅은 컴포넌트가 렌더링 될 때마다 같은 순서로 호출되어야 하기 때문에
조건문이나 반복문 안에서 훅을 호출하면 리액트가 훅의 호출 순서를 추적할 수 없게 되어 예기치 않은 동작을 초래할 수 있다.
따라서 훅을 사용할 때는 항상 위의 규칙들을 지켜야만 리액트가 올바르게 동작하고,
예상대로 상태 변경 및 부수 효과 처리가 이루어질 수 있다.
결론
1. 리액트 훅은 상태 관리와 부수 효과 처리 작업을 매우 간편하게 해 준다.
2. useState, useEffect, useContext와 같은 기본 훅을 잘 활용하면 리액트 컴포넌트를 더 쉽게 만들 수 있다.
3. Zustand와 같은 라이브러리 역시 훅을 활용해 전역 상태를 쉽게 관리하고 공유할 수 있게 도와준다.
=> 리액트 훅을 제대로 이해하고 사용하면 더 깔끔하고 효율적인 코드를 작성할 수 있다.
'FRONTEND > React' 카테고리의 다른 글
| 리액트 훅 | useState에 대해 알아보기 (4) | 2025.08.08 |
|---|---|
| 부모 요소 패딩 값에 영향을 받지 않고 전체 너비 구분선 그리기 (0) | 2025.04.04 |
| 모던 리액트 Deep Dive | 1.1 자바스크립트와 동등 비교 (0) | 2025.03.09 |
| 리액트 컴포넌트 기반 구조 이해하기(WITH 재사용성, 유지보수성) (0) | 2025.01.05 |
| React 프로젝트 생성 with Vite & Tailwind (2) | 2024.12.26 |