[React] React.js 강좌 9. Hooks 기본 – useState와 useEffect 완벽 이해
1. Hooks란 무엇인가?
리액트 16.8 버전부터 등장한 **Hooks(훅)**은
기존 클래스형 컴포넌트에서만 가능했던 기능(상태 관리, 생명주기 제어 등)을
함수형 컴포넌트에서도 사용할 수 있게 해주는 기능입니다.
과거에는 리액트에서 상태를 다루려면 반드시 클래스형 컴포넌트를 써야 했습니다.
class Counter extends React.Component {
state = { count: 0 };
render() {
return (
<div>
<p>현재 카운트: {this.state.count}</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
+1
</button>
</div>
);
}
}
하지만 Hooks가 등장하면서 함수형 컴포넌트에서도 이렇게 간단히 작성할 수 있게 되었습니다.
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>현재 카운트: {count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}
즉, Hooks는 리액트를 더 단순하고 직관적으로 사용하는 방법이라고 할 수 있습니다.
2. useState – 상태 관리의 기본
useState는 리액트의 가장 기초적인 Hook입니다.
이 훅은 컴포넌트 내부에서 “상태(State)”를 생성하고 관리할 수 있게 해줍니다.
기본 문법
const [state, setState] = useState(initialValue);
- state: 현재 상태 값
- setState: 상태를 업데이트하는 함수
- initialValue: 상태의 초기값
예를 들어, 숫자 카운터를 구현해보면 아래와 같습니다.
function Counter() {
const [count, setCount] = useState(0);
const increase = () => setCount(count + 1);
const decrease = () => setCount(count - 1);
return (
<div>
<h3>카운터</h3>
<p>현재 값: {count}</p>
<button onClick={increase}>+1</button>
<button onClick={decrease}>-1</button>
</div>
);
}
버튼을 클릭할 때마다 setCount()가 호출되어 상태가 바뀌고,
리액트는 그 상태에 맞게 자동으로 UI를 다시 렌더링합니다.
3. useState는 비동기적으로 작동한다
setState()는 바로 값을 바꾸는 게 아니라,
다음 렌더링 시점에 변경된 값을 반영합니다.
즉, 다음과 같은 코드는 예상과 다르게 작동할 수 있습니다.
function Counter() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
};
return <button onClick={handleClick}>{count}</button>;
}
이 경우 count는 1만 증가합니다.
왜냐하면 리액트는 count의 현재 값(0)을 기준으로 세 번 호출하지만,
렌더링은 한 번만 일어나기 때문입니다.
이를 해결하려면 함수형 업데이트 방식을 사용해야 합니다.
setCount((prev) => prev + 1);
setCount((prev) => prev + 1);
setCount((prev) => prev + 1);
이렇게 하면 최종적으로 count는 3 증가합니다.
4. useEffect – 생명주기 제어
useEffect는 함수형 컴포넌트에서 **생명주기(Lifecycle)**를 제어하는 훅입니다.
이 훅은 “렌더링 이후에 어떤 일을 할지”를 지정합니다.
기본 문법은 다음과 같습니다.
useEffect(() => {
// 실행할 코드
}, [의존성]);
5. useEffect의 실행 시점
useEffect는 기본적으로 컴포넌트가 렌더링된 직후 실행됩니다.
또한 **의존성 배열(dependency array)**을 사용해 실행 시점을 제어할 수 있습니다.
(1) 의존성 배열이 없는 경우 – 매 렌더링마다 실행
useEffect(() => {
console.log('렌더링마다 실행됩니다.');
});
(2) 빈 배열 [] – 처음 한 번만 실행 (mount 시)
useEffect(() => {
console.log('컴포넌트가 처음 렌더링될 때만 실행됩니다.');
}, []);
(3) 특정 값이 바뀔 때만 실행
useEffect(() => {
console.log('count가 바뀔 때만 실행됩니다.');
}, [count]);
6. useEffect에서의 정리(clean-up)
useEffect는 반환값으로 **정리 함수(clean-up function)**를 받을 수 있습니다.
이는 컴포넌트가 사라질 때(unmount) 또는 다음 effect가 실행되기 전에 호출됩니다.
useEffect(() => {
const timer = setInterval(() => {
console.log('1초마다 실행');
}, 1000);
// 정리 함수
return () => {
clearInterval(timer);
console.log('타이머 정리');
};
}, []);
이 코드는 타이머를 생성하고,
컴포넌트가 사라질 때 타이머를 제거합니다.
이 기능은 메모리 누수 방지에 매우 중요합니다.
7. useEffect의 대표적인 사용 예시
- API 호출
- useEffect(() => { fetch('/api/users') .then((res) => res.json()) .then((data) => console.log(data)); }, []);
- 이벤트 리스너 등록/해제
- useEffect(() => { const handleResize = () => console.log('창 크기 변경'); window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, []);
- 상태 변화 감지
- useEffect(() => { console.log('count가 바뀌었습니다:', count); }, [count]);
8. useState와 useEffect 함께 사용하기
두 훅은 함께 자주 사용됩니다.
예를 들어, 카운트가 바뀔 때마다 콘솔에 출력하는 예제입니다.
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log(`현재 카운트: ${count}`);
}, [count]);
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}
useEffect는 count의 변경을 감지하여
렌더링 이후 필요한 부가 작업(side effect)을 수행합니다.
9. 마무리
이번 강의에서는 Hooks의 핵심인 useState와 useEffect를 다뤘습니다.
요약하면 다음과 같습니다.
- useState → 상태(state)를 관리하는 기본 훅
- useEffect → 컴포넌트의 생명주기(Lifecycle)를 제어하는 훅
- setState는 비동기적으로 작동하며, 함수형 업데이트로 안정적으로 값을 변경할 수 있다.
- useEffect는 렌더링 후 실행, 의존성 배열로 실행 시점을 제어한다.
- 정리(clean-up) 함수는 메모리 누수를 방지하는 핵심 요소다.
다음 글에서는 컴포넌트 간 데이터 전달 구조를 다루는
**상태 끌어올리기(Lifting State Up)**와 Context API에 대해 배워보겠습니다.