리액트에서 함수형 컴포넌트는 이름 그대로 그냥 함수다.
즉, 상태(state)가 바뀌면 전체 함수가 다시 실행되며, 그에 따라 UI도 새롭게 그려진다.
하지만 이때 단순히 화면을 다시 그리는 것만으로는 부족하다.
상태를 기억하고, 외부 작업(API 호출, 타이머 등)을 제대로 제어하려면 useState와 useEffect가 필요하다.

유저는 리액트 앱에서 서버와 인터랙션을 한다.
예를 들어 회원가입, 로그인, 블로그 포스팅 같은 작업은 서버와 통신하며 DB에 정보가 저장된다.

하지만 댓글을 작성하기 직전, 즉 유저가 입력을 끝내기 전까지는 그 내용이 어디에 저장될까?
일반적으로 클라이언트 사이드의 메모리 위에 존재한다.
실시간으로 모든 입력값을 DB에 저장한다면, 유저의 조작 하나하나에 네트워크 요청이 발생해 성능 저하와 불편함이 생긴다.
따라서 클라이언트 쪽에서 입력값을 잠시 기억해야 하며, 이 역할을 state가 담당한다.
function MyComponent() {
console.log("렌더링됨"); // 상태가 바뀔 때마다 다시 실행
return <div>Hello</div>;
}
useEffect 실행const [count, setCount] = useState(0);
setCount(count + 1); // 상태 변경 → 리렌더링
| 항목 | 설명 |
|---|---|
| 역할 | 상태 저장 및 변경 |
| 실행 시점 | 컴포넌트 함수가 호출될 때마다 상태값을 React가 연결 |
| 상태 변경 시 | 컴포넌트가 리렌더링됨 |
| 예시 용도 | 입력값, 토글, 카운터, 체크 여부 등 |
useEffect(() => {
console.log("마운트됨");
return () => {
console.log("언마운트 또는 다음 effect 직전 정리");
};
}, []);
| 항목 | 설명 |
|---|---|
| 역할 | 부수효과(Effect) 실행: DOM 조작, API 호출 등 |
| 실행 시점 | 렌더링이 끝난 후 (DOM 반영 후) |
| 의존성 배열(deps) | 변화 감지 대상 지정 → 값이 바뀔 때마다 다시 실행 |
| cleanup 함수 | 이전 effect 정리용 (언마운트 시에도 실행됨) |
예시: 요소의 크기를 측정할 때
useEffect(() => { const box = document.getElementById("box"); console.log(box?.offsetHeight); // 박스의 실제 높이 }, []);
- DOM이 그려지기 전에 실행된다면 offsetHeight가 0이 나오거나 잘못된 값이 나올 수 있다.
function Example() {
const [count, setCount] = useState(0);
console.log("렌더링");
useEffect(() => {
console.log("🟢 effect 실행됨");
return () => {
console.log("🔴 cleanup 실행됨");
};
}, [count]);
return <button onClick={() => setCount(c => c + 1)}>+</button>;
}
실행 순서 예시:
count 초기값 0 → "렌더링" 출력"🟢 effect 실행됨" 출력setCount(1) → 상태 변경"렌더링" 출력"🔴 cleanup 실행됨" 출력"🟢 effect 실행됨" 출력| 항목 | useState | useEffect |
|---|---|---|
| 실행 시점 | 함수 실행 시 | 렌더링이 끝난 후 (DOM 적용 후) |
| 역할 | 상태 저장 및 리렌더링 유발 | 외부 작업 처리 (API, 타이머 등) |
| 리렌더링 유발 | O (setState() 호출 시) | X (내부에서 상태 변경하지 않으면 리렌더링 없음) |
| cleanup | 없음 | return으로 이전 effect 정리 |
| 주요 용도 | 사용자 입력값, 토글, 내부 상태 관리 | 비동기 작업, 타이머, 이벤트 등록, 웹소켓 등 관리 |
| 생명주기 단계 | 설명 | 관련 Hook |
|---|---|---|
| Mounting | 컴포넌트가 처음 나타날 때 | useEffect(() => { ... }, []) |
| Updating | props 또는 state가 변경될 때 | useEffect(() => { ... }, [deps]) |
| Unmounting | 컴포넌트가 사라질 때 | useEffect(() => { return () => {...} }, []) |
| 생명주기 단계 | 클래스형 메서드 | 함수형 대응 방식 |
|---|---|---|
| 마운트 | componentDidMount() | useEffect(() => {...}, []) |
| 업데이트 | componentDidUpdate() | useEffect(() => {...}, [deps]) |
| 언마운트 | componentWillUnmount() | useEffect(() => { return () => {...} }, []) |
함수형에서는 생명주기를
useEffect로 모두 커버할 수 있다.
useEffect(() => {
fetch('/api/data')
.then(res => res.json())
.then(data => setData(data));
}, []);
useEffect(() => {
const timer = setInterval(() => console.log('⏰'), 1000);
return () => clearInterval(timer);
}, []);
useEffect(() => {
const handleScroll = () => console.log("scroll!");
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, []);
| 포인트 | 내용 |
|---|---|
| 상태란? | 사용자 인터랙션 중 일시적으로 메모리에 저장되는 클라이언트 측 데이터 |
useState | 상태 저장 + 상태 변경 시 리렌더링 발생 |
useEffect | 외부 작업(API, 타이머 등)을 렌더링 이후 타이밍에 실행 |
| 전체 흐름 | 상태 바뀜 → 함수 실행 → 화면 반영 → effect 실행 |
| 클래스형과의 차이 | useEffect 하나로 모든 생명주기 단계 제어 가능 |