본문 바로가기

리액트

⭐ 4편 — 리액트 핵심 Hook 완전 정복: useState와 useEffect 제대로 쓰는 법

⭐ 4편 — 리액트 핵심 Hook 완전 정복: useState와 useEffect 제대로 쓰는 법

리액트에서 화면의 변화를 만들고, API를 호출하고, 컴포넌트의 생명주기를 다루려면
무조건 알아야 하는 훅(Hook)이 바로 useState와 useEffect입니다.
많은 초보자들이 여기에서 발목이 잡히는데, 대부분 공통된 실수에서 출발합니다.
이번 글에서는 “왜 그렇게 동작하는지”를 중심으로 경험 기반으로 설명해드릴게요.


✅ 1. useState — 리렌더링의 출발점

useState는 리액트에서 내부 상태를 저장하는 가장 기본적인 방식입니다.
값이 바뀌면 자동으로 리렌더링 되기 때문에 UI가 즉시 업데이트됩니다.
✔ 기본 문법
const [value, setValue] = useState(초기값);
예를 들어 숫자를 증가시키는 카운터라면:
const [count, setCount] = useState(0);


🔍 왜 setState를 쓰면 화면이 다시 그려질까?

리액트는 state가 바뀌면 → 전체 컴포넌트를 다시 렌더링하는 구조입니다.
이때 DOM을 통째로 다시 그리는 것이 아니라,
바뀐 부분만 가상 DOM이 비교해서 최적화된 방식으로 업데이트합니다.
이 구조 덕분에 개발자는 DOM 조작을 직접 할 필요가 없고,
UI가 항상 데이터와 정확하게 일치하게 됩니다.
✔ setState 호출 시 주의할 점
❗ 1) 값이 비동기적으로 반영된다
아래 코드는 잘못된 예입니다:
setCount(count + 1);
setCount(count + 1);
이렇게 하면 1만 증가합니다.
왜냐하면 count가 업데이트되기 전에 같은 값을 두 번 더하기 때문이에요.
✔ 해결법: 함수형 업데이트
setCount(prev => prev + 1);
setCount(prev => prev + 1);
이렇게 작성하면 정상적으로 2가 증가합니다.


✅ 2. useEffect — 리액트의 생명주기 관리

useEffect는 컴포넌트가 렌더링될 때 특정 값이 바뀔 때 사라질 때
실행되는 함수입니다.
초보자들이 가장 많이 어려워하는 부분이기 때문에 이걸 “언제 실행되는지” 중심으로 풀어드릴게요.


🔥 useEffect를 이해하는 핵심 1줄

✔ useEffect는 “렌더링 후” 실행된다.


즉, 화면이 그려진 다음에 실행되는 추가 작업 함수라고 보면 됩니다.
✔ useEffect 기본 문법
useEffect(() => {
// 실행될 코드
}, [의존성]);
의존성 배열이 핵심입니다.
여기에 무엇을 넣느냐에 따라 실행 시점이 달라집니다.


📌 useEffect 실행 시점 정리

1) 의존성 배열이 없는 경우 (매 렌더링마다 실행)
useEffect(() => {
console.log("렌더링됨");
});
컴포넌트가 렌더링될 때마다 실행됩니다.
2) 빈 배열인 경우 (첫 렌더링 한 번만 실행)
useEffect(() => {
console.log("처음 한 번만 실행");
}, []);
이 패턴은 보통 **초기 데이터 로딩(API 호출)**에 많이 사용됩니다.
3) 특정 값이 바뀔 때만 실행
useEffect(() => {
console.log("count가 변경될 때 실행!");
}, [count]);
이건 감시(watch) 기능이라고 생각하면 이해가 빠릅니다.


📌 useEffect 대표적인 사용 예

✔ 1) API 호출
컴포넌트가 처음 화면에 나타날 때
useEffect(() => {
fetchData();
}, []);
✔ 2) 이벤트 등록/해제
스크롤 이벤트, resize 이벤트 등
useEffect(() => {
const handleScroll = () => console.log("scroll");
window.addEventListener("scroll", handleScroll);
return () => {
window.removeEventListener("scroll", handleScroll);
};
}, []);
여기서 return 함수는 “정리(clean-up)” 역할을 합니다.
✔ 3) 특정 값 변경될 때 동작
useEffect(() => {
console.log(`검색어 변경됨: ${keyword}`);
}, [keyword]);
실무에서는 검색 자동완성 구현할 때 자주 씁니다.


🔥 초보자가 useEffect에서 자주 하는 3가지 실수

❌ 1) 의존성 배열을 비워두면 되는 줄 앎
데이터가 바뀌어도 다시 실행되지 않으면 UI가 맞지 않게 됩니다.
무조건 필요한 값을 넣어줘야 합니다.
❌ 2) useEffect 안에서 state를 과도하게 변경
state가 바뀌면 → 렌더링 → useEffect 실행 → 또 state 변경
이렇게 무한 루프가 발생합니다.
❌ 3) 비동기 함수를 직접 useEffect에 넣음
useEffect(async () => { ... }, []);
이건 잘못된 패턴입니다.
✔ 해결 방법:
useEffect(() => {
async function load() {
await fetchItems();
}
load();
}, []);

✨ 마무리: useState와 useEffect를 정확히 이해하면 대부분의 리액트 코드를 읽을 수 있다

리액트 학습에서 가장 큰 벽이 바로 useState와 useEffect입니다.
이 두 개념만 제대로 이해해도 리액트 전체의 작동 원리가 눈에 보이고,
프로젝트 구조가 훨씬 명확하게 느껴집니다.
다음 글에서는 리액트에서 아주 중요한 또 하나의 개념, useRef(레퍼런스) 활용법을 실전 중심으로 풀어드릴게요.
DOM 제어, 저장소 역할, 포커스 관리 등등 실무에서 정말 자주 쓰이는 기능입니다.