Web/React

[React] Hook이 대체 뭐야? (useState, useEffect)

Devyne 2025. 6. 30. 14:05
반응형

리액트 훅(Hook)이란?

저번에 컴포넌트에 대해서 간단하게 설명했을 때 안한 얘기가 하나 있다.

컴포넌트는 크게 두 가지 유형으로 나뉜다.

클래스형 컴포넌트와 함수형 컴포넌트이다.

클래스형 컴포넌트는 예전부터 사용하던 방식으로, 이름처럼 ‘클래스’ 라는 문법으로 만든다. 기능은 많지만 코드가 조금 길고 복잡하게 느껴질 수 있다.

함수형 컴포넌트는 비교적 최근에 등장해 대세가 된 방식이다. ‘함수’ 형태로 훨씬 간결하고 직관적으로 컴포넌트를 만들 수 있다.

이 함수 컴포넌트는 마치 ‘주문을 받으면 특정 모양의 레고 블록(UI)을 만들어서 내놓는 간단한 기계’ 와 같다.

그런데 이 간단한 기계에는 몇 가지 치명적인 단점이 있다.

  • 기억력이 없다. (Stateless) : 버튼을 몇 번 눌렀는지, 사용자가 입력창에 무엇을 썼는지 등을 전혀 기억하지 못한다.
  • 외부 세상과 소통할 수 없다. : 만들어진 후에 외부 서버에서 데이터를 가져오거나, 시간이 지나면 특정 행동을 하도록 설정할 수가 없다.

이런 부족한 점을 보완하기 위해, 함수 컴포넌트에 ‘특별한 능력’을 갈고리(Hook)처럼 걸어서 사용할 수 있게 만든 것이 바로 리액트 훅이다.

  • useState 훅 : 컴포넌트에 ‘기억력’ 을 부여하는 갈고리
  • useEffect 훅 : ‘외부 세상과 소통하는 능력’ (Side Effects 처리)을 부여하는 갈고리

왜 훅(Hook)이 등장했을까?

훅이 등장하기 전, ‘기억력(state)’이나 ‘외부와의 소통(lifecyclce)’ 같은 기능들은 오직 클래스 컴포넌트만 사용할 수 있었다. 하지만 클래스 컴포넌트는 다음과 같은 문제점들을 가지고 있었다.

  • 로직 재사용의 어려움 (Wrapper Hell)
    • 컴포넌트 간에 상태 관련 로직을 재사용하기가 매우 까다롭다.이를 해결하기 위해 HOC(Higher-Order Components)나 Render Props 같은 복잡한 패턴을 사용해야 하는데, 이로 인해 컴포넌트 트리가 불필요하게 깊어지고, 코드 추적이 어려워지는 ‘Wrapper Hell’ 현상이 발생했다.
  • 거대하고 복잡해지는 컴포넌트
    • 하나의 클래스 컴포넌트 안에 관련 없는 여러 로직들이 componentDidMonut , componentDidUpdate 같은 생명주기 메소드에 섞어 들어갔다. 예를 들어, componentDidMount 안에는 API 데이터 호출, 이벤트 리스너 등록 등 서로 다른 기능의 코드들이 한데 뒤섞여 있었다. 반대로, 하나의 기능을 위한 코드는 componentDidMount 와 componentWillUnmount 에 흩어져 있어 코드를 이해하고 유지보수하기 어려웠다.
  • 클래스의 장벽
    • JavaScript의 this 키워드는 많은 개발자들을 혼란스럽게 만들었고, 이벤트 핸들러를 매번 바인딩하는 등 불필요한 코드가 많았다.

훅(Hook)은 이 문제들을 해결하기 위해 등장했다. 훅을 통해 개발자들은 함수 컴포넌트 내에서 상태 로직을 간결하게 재사용하고, 관련 있는 코드들을 하나로 묶어 관리하며, 복잡한 클래스 문법 없이도 React의 모든 기능을 활용할 수 있게 되었다.


핵심 훅(Hook) 알아보기: useState 와 useEffect

대표적인 훅인 useState 와 useEffect 에 대해서 알아보자.

 

useState : 상태(State)를 위한 훅

 

useState 는 함수 컴포넌트에 ‘기억력’, 즉, 상태(State)를 추가해주는 훅이다.

import { useState } from 'react';

function Counter() {
	// useState 사용: count라는 '상태'를 생성. 초기값은 0
	// count: 현재 상태값 (읽기 전용) (getter) 
	// setCount: 이 상태를 업데이트할 수 있는 함수 (setter) 
	const [countㄷ, setCount] = useState(0);
	
	return (
		<div>
			<p>You clicked {count} times</p>
			<button onClick={() => setCount(count+1) }>
			Click me
			</button>
		</div>
	);

}
  • setCount 함수가 호출되어 count 상태가 변경되면, React는 이 Counter 컴포넌트가 리렌더링되는 것을 인지한다. 그리고 컴포넌트를 리렌더링하여 화면의 숫자를 업데이트한다.

useEffect 이 사이드 이펙트(Side Effect)를 위한 훅

 

useEffect 는 컴포넌트가 렌더링된 이후에 처리해야 하는 사이드 이펙트를 수행하기 위한 훅이다. 여기서 사이드 이펙트란, 렌더링 결과에 직접적인 영향을 주지 않는 모든 부수적인 작업들을 의미한다.

  • 사이드 이펙트 예시:
    • API를 통해 서버에서 데이터 가져오기 (Data Fetching)
    • 외부 라이브러리 사용
    • DOM 직접 조작 (예: 문서 제목 변경)
    • 타이머 설정(setTimeout, setInterval)
    • 이벤트 리스너 구독 및 해제
import { useState, useEffect } from 'react';

function DocumentTitleChanger() {
	const [count, setCount] = useState(0); 
	
	// useEffect 사용
	// 이 컴포넌트는 '문서의 제목을 바꾸는' 부수적인 임무를 가짐
	useEffect( () => {
		// 렌더링이 완료된 후에 이 코드가 실행됨
		document.title = `You clicked ${count} times`; 
	}, [count]); // '의존성 배열': count가 변경될 때만 이 effect를 실행하라 

	return (
		<button onClick={ () => setCount(count+1)}>
			Update Title ({count})
		</button>
	);
}
  • 의존성 배열: useEffect 의 두 번째 인자인 [count] 는 매우 중요하다. 이것은 React에게 “이 useEffect 안의 코드는 count 라는 값이 변경될 때만 다시 실행해줘!” 라고 알려주는 역할을 한다.
    • [count] : count 가 바뀔 때마다 실행된다.
    • [] (빈 배열) : 최초 렌더링 직후 단 한 번만 실행된다. (API 최초 호출 등에 사용)
    • 생략 시 : 매 리렌더링마다 실행된다. (주의해서 사용해야함)

훅의 규칙 (Rules of Hooks)

훅은 매우 편리하지만, 올바르게 동작하기 위해 반드시 지켜야 할 두 가지 규칙이 있다.

  1. 최상위 레벨에서만 호출해야 한다.
  2. 반복문(for), 조건문(if), 중첩된 함수 안에서 훅을 호출해서는 안된다. 훅은 항상 컴포넌트의 최상위 레벨에서, 동일한 순서로 호출되어야 한다. 이는 React가 여러 useState 와 useEffect 호출 사이에서 올바른 상태를 유지하는 방식과 관련이 있다.
  3. 오직 리액트 함수 내에서만 호출해야 한다.
  4. 리액트 함수 컴포넌트나 커스텀 훅 안에서만 훅을 호출할 수 있다. 일반적인 JavaScript 함수 안에서는 호출할 수 없다.
반응형