[리액트 훅 가이드 1편] 훅(Hook)은 왜 등장했을까? (feat. `useState`, `useEffect`) | Moomoocow Devlog
- 왜 렌더 함수 안에서 사이드 이펙트를 하면 안 되는지
- 왜
useEffect cleanup이 언마운트 때만 도는 게 아닌지
- 왜 Strict Mode에서 effect가 두 번 도는 것처럼 보이는지
결국 클래스 메서드 이름을 외우는 게 목적이 아니라,
React의 실행 모델(Trigger -> Render -> Commit -> Effect)을 이해하는 게 목적이라는 점이 명확해졌다.
2. 상태 관리의 기본: useState
useState는 함수형 컴포넌트에서 상태를 다루는 기본 훅이다.
타입스크립트와 같이 쓰면 안정성이 확실히 좋아진다.
import { useState } from "react";
interface Member {
name: string;
age: number;
}
export default function MemberList() {
const [memberList, setMemberList] = useState<Member[]>([]);
return null;
}
useState<Member[]>처럼 제네릭을 명시하면 상태 구조를 컴파일 단계에서 보장받는다.
예를 들어 age를 agee로 오타 내면 런타임 전에 잡힌다.
- 런타임 잠복 버그 감소
- 리팩터링 안정성 향상
- 상태 구조 변경 시 영향 범위 파악이 쉬움
3. 사이드 이펙트 관리: useEffect
렌더링 이후에 발생하는 작업(데이터 요청, 이벤트 구독, 타이머 등)을 사이드 이펙트라고 하고, 이걸 다루는 훅이 useEffect다.
useEffect(() => {
// effect
return () => {
// cleanup
};
}, [deps]);
- 첫 번째 인자: 실행할 콜백
- 두 번째 인자: 의존성 배열(
deps)
의존성 배열 값이 바뀌면 effect가 다시 실행된다.
3-1. 의존성 배열과 얕은 비교(Shallow Compare)
useEffect는 의존성 비교 시 얕은 비교를 한다.
그래서 객체/배열은 내부 값이 같아도 참조가 바뀌면 재실행된다.
- 지양:
[user]
- 권장:
[user.id, user.name]
이렇게 해야 불필요한 effect 재실행을 줄일 수 있다.
3-2. 클린업 함수로 정리까지 한 번에
클래스의 componentWillUnmount 역할을 useEffect 반환 함수가 담당한다.
- 언마운트 시 실행
- effect 재실행 직전에도 실행
등록/해제를 같은 블록에 둘 수 있다는 점이 정말 크다.
관심사 단위로 코드가 묶여서 읽기 쉬워진다.
3-3. useEffect 남발하면 생기는 문제
공부하면서 제일 경계하게 된 부분이 useEffect 남발이다.
- props/state로 바로 계산 가능한 값을
useEffect + setState로 다시 저장
- 사용자 이벤트 처리 로직을 effect로 우회
- 화면 전환 조건을 effect 안에만 숨김
- 불필요한 렌더 증가
- 의존성 배열 꼬임
- 실행 원인 추적 어려움
- 로직 분산으로 유지보수 난이도 상승
useEffect 사용 기준
- 렌더 결과만으로 계산 가능하면: effect 쓰지 않기
- 이벤트 핸들러로 처리 가능하면: effect 쓰지 않기
- 외부 시스템 동기화(서버, DOM API, 구독, 타이머)가 필요하면: effect 사용
useEffect는 “렌더 후 외부 세계와 동기화”할 때만 쓴다.
클래스 생명주기와 훅 대응표
클래스 메서드를 완전히 1:1 치환하는 건 아니지만, 자주 보이는 대응은 아래처럼 이해하면 좋다.
| 클래스 컴포넌트 생명주기 | 함수형 컴포넌트 훅 패턴 | 메모 |
|---|
componentDidMount | useEffect(() => { ... }, []) | 최초 마운트 후 1회 실행 의도 |
componentDidUpdate | useEffect(() => { ... }, [deps]) | 특정 값 변경 시 재실행 |
componentWillUnmount | useEffect(() => { return () => { ... } }, []) | cleanup에서 해제 로직 |
| 마운트 + 언마운트 분산 로직 | useEffect 한 블록에 등록/해제 함께 작성 | 관심사 단위로 응집도 향상 |
렌더 전후 제어(getSnapshotBeforeUpdate) | useLayoutEffect (필요 시) | DOM 측정/동기 반영처럼 민감한 경우 |
useEffect는 생명주기 흉내 도구라기보다 의존성 기반 동기화 도구에 가깝다.
- 클래스 치환 관점만으로 접근하면 의존성 설계에서 실수하기 쉽다.
마무리
이번 정리를 하면서, 훅은 단순 문법이 아니라 리액트 코드 구조를 바꾼 전환점이라는 걸 다시 느꼈다.
- 생명주기 분산 문제 완화
- 관심사 중심 구성 가능
- 상태 로직 재사용성 향상
참고 이미지/문서 링크
생명주기 순서와 렌더/커밋 흐름을 시각적으로 보는 데 도움이 됐던 링크들:
댓글 0개
첫 댓글을 남겨보세요.