


상태는 어디에 둬야 하는가
— React 설계의 80%를 결정하는 단 하나의 판단
React에서 제일 어려운 질문은 이거다.
이 상태를
이 컴포넌트에 둘 것인가,
부모로 올릴 것인가,
아니면 전역으로 뺄 것인가
이 결정을 잘못하면
- useEffect가 늘어나고
- props가 꼬이고
- 전역 상태가 쓰레기장이 된다
이번 글은 명확한 판단 기준을 남기는 게 목적이다.
1️⃣ 상태의 “주인”이라는 개념부터 잡자
React에서 state는 소유자(owner) 가 있어야 한다.
그 상태를 “변경할 책임”이 있는 컴포넌트
이 기준이 없으면
상태는 떠돌아다니기 시작한다.
2️⃣ 가장 먼저 고려할 선택지: 로컬 상태
function SearchInput() {
const [value, setValue] = useState("");
return (
<input
value={value}
onChange={e => setValue(e.target.value)}
/>
);
}
이 상태는 명확하다.
- 입력값은 이 컴포넌트 안에서만 쓰인다
- 외부에서 알 필요도 없다
- 외부에서 바꾸면 오히려 이상하다
👉 이게 로컬 상태의 교과서적인 예시
형제 컴포넌트가 같은 값을 필요로 할 때
판단 기준 1:
이 상태를 이 컴포넌트 말고
누가 필요로 하는가?
→ “아무도 없음”이면 여기 둔다
3️⃣ 상태를 끌어올려야 하는 순간 (lift state up)
이제 이 상황을 보자.
형제 컴포넌트가 같은 값을 필요로 할 때
function Parent() {
return (
<>
<SearchInput /> //Input의 state를 Result에서 쓰고 싶은데 props로 못받음 useEffect로 바로 올리는건 위험의 시작
<SearchResult />
</>
);
}
SearchInput의 값으로
SearchResult가 결과를 보여줘야 한다면?
👉 상태는 Input의 것이 아니다
function Parent() {
const [keyword, setKeyword] = useState("");
return (
<>
<SearchInput value={keyword} onChange={setKeyword} />
<SearchResult keyword={keyword} />
</>
);
}
판단 기준 2:
여러 컴포넌트가
같은 상태를 읽거나 변경해야 하는가?
→ 그렇다면 공통 부모가 주인이다
이게 “상태 끌어올리기”의 전부다.
기술이 아니라 책임 분리 문제다.
4️⃣ props drilling은 언제 문제가 되는가
<Layout>
<Page>
<Section>
<SearchInput
value={keyword}
onChange={setKeyword}
/>
</Section>
</Page>
</Layout>
이걸 보고 흔히 이런 말을 한다.
“props drilling이니까 전역으로 빼자”
대부분 너무 빠른 결론이다.
function ChildButton() {
const [loggedIn, setLoggedIn] = useState(false);
return (
<button onClick={() => setLoggedIn(true)}>
로그인
</button>
);
}
// 로그인 상태를 다른 컴포넌트에서 알 수 있는 방법은..?
// (헤더, 라우터, 메뉴, 권한체크...? )
// 알 수 없음. 자식 안에 숨어있으니까. 이 구조는 이벤트도 자식, 결과도 자식이라
// 확장 불가능하다
props drilling은 “문제”가 아니라 “증상”이다
어떤 상태/행동을
실제로 쓰지도 않는 중간 컴포넌트들이
그저 전달만 하기 위해 props를 “뚫고 지나가는” 구조. (좋지 않은 것)
- 중간 컴포넌트가 props를 사용하지 않는다
- 단순 전달만 한다
이게 불편한 이유는 하나다.
👉 상태의 주인이 너무 멀리 있다
해결책은 전역 상태가 아니라
컴포넌트 경계 재설계인 경우가 많다.

컴포넌트 재설계
function Parent() {
const [loggedIn, setLoggedIn] = useState(false);
function handleLogin() {
setLoggedIn(true);
}
return <ChildButton onClick={handleLogin} />;
}
function ChildButton({ onClick }) {
return <button onClick={onClick}>로그인</button>;
}
// 이 구조는
// 클릭은 자식에서 발생
// 로그인 상태 변경은 부모가 책임
// 즉, 자식만 알아도 되는 자식상태라면 그대로 두고
// 형제/부모/라우터가 알아야 하는 상태라면 부모상태로 뺀다
** props drilling이지만 좋은 구조도 있다. 공통 컴포넌트는 props drilling이 기본 설계다.
다만 “아무 생각 없이 내려보낸 props drilling”과
“의도를 가진 props 전달”은 완전히 다른 물건이다.
차근차근 분해해보자.
1️⃣ 먼저 정리부터 — props drilling이 왜 욕을 먹나
props drilling이 싫어지는 순간은 보통 이때다.
- B, C는 값을 쓰지도 않는데
- 그냥 D에 전달하기 위해 존재함
- 구조를 바꾸면 연쇄 수정 발생
이건 drilling 자체가 아니라 설계 부재가 문제다.
2️⃣ props drilling이지만 “좋은 설계”인 핵심 조건
좋은 props drilling에는 공통된 특징이 있다.
✅ 1. “데이터 흐름”이 명확하다
누가 소유하고, 누가 소비하는지가 선명함
- user의 소유자: App
- Page는 레이아웃 역할
- UserProfile이 실제 소비자
Page는 “파이프”라는 역할이 분명하다.
📌 이건 정보 손실 없는 전달이다.
✅ 2. 중간 컴포넌트가 의미를 가진다
여기서
- Form
- FormSection
둘 다 도메인 개념이다.
“그냥 통과 노드”가 아니다.
👉 이런 drilling은 구조를 설명하는 역할을 한다.
✅ 3. 데이터 수명이 짧다 (페이지 스코프)
아래 케이스는 거의 정석이다.
- 페이지 벗어나면 데이터 소멸
- 전역으로 올릴 이유 없음
- Context 쓰면 오히려 추적이 어려워짐
📌 “한 화면에서만 쓰는 데이터”는 drilling이 맞다.
3️⃣ 실무에서 아주 흔한 “좋은 drilling” 예
🔹 레이아웃 + 슬롯 구조
- Layout은 user를 쓰지 않아도
- “이 레이아웃은 user 컨텍스트를 전제로 한다”는 의미 전달
이건 UI 계약(contract) 이다.
🔹 테이블 / 리스트 계열 컴포넌트
안 내려보내면?
- Context 남발
- 재사용성 붕괴
- 테스트 난이도 폭증
📌 공통 컴포넌트는 props drilling이 기본 설계다.
🔹 이벤트 콜백 전달
이건 drilling이 아니라 의사결정권 위임이다.
“결과는 부모가 책임진다”
이 구조를 깨면
- 팝업이 비즈니스 로직을 갖기 시작한다
- 재사용 불가해진다
4️⃣ 그럼 언제 나쁜 drilling이 되는가 (경계선)
아래 중 하나라도 걸리면 위험 신호다.
- 중간 컴포넌트가 의미 없이 전달만 한다
- props 이름이 data, value, info처럼 추상적
- 동일한 props를 5단계 이상 전달
- 중간에서 타입이 바뀌거나 가공됨
- “왜 여기까지 내려왔는지” 설명이 안 됨
이때는:
- Context
- 합성 컴포넌트
- children 패턴
- 상태 소유 위치 재조정
중 하나를 고려해야 한다.
5️⃣ 중요한 관점 하나
props drilling은 기술 문제가 아니다
👉 의사소통 문제다
좋은 drilling은 이런 문장이 성립한다.
“이 값은 여기서 만들어졌고
이 구조 전체에서 의미가 있다”
나쁜 drilling은 설명이 안 된다.
“그냥 필요해서 내려보냈어요…”
6️⃣ 한 줄 결론
- props drilling은 악이 아니다
- 의도 없는 drilling만 나쁘다
- 구조를 설명하는 전달은 오히려 가장 안전한 설계다
비유로 설명하면 (가장 잘 와닿는다)
주문 버튼 예시
- 자식: “주문하기” 버튼
- 이벤트: 버튼 클릭
- 부모: 주문 목록, 결제 상태, 재고
버튼이 이런 걸 직접 하면 이상하다.
👉 버튼은 의사결정권자가 아니다.
버튼은 말만 한다.
“사용자가 나 눌렀어요”
결정은 부모가 한다.
“사용자의 행동은
가장 가까운 컴포넌트에서 감지하고,
그 행동의 의미와 결과는
더 큰 맥락을 아는 컴포넌트가 결정한다”

5️⃣ 전역 상태는 언제 써야 하나 (정말로)
전역 상태를 써도 되는 조건은 명확하다.
1. 앱 전반에서 필요하고
2. 여러 화면에서 공유되고
3. 생명주기가 길고
4. 누가 바꿔도 의미가 같은 상태
대표적인 예:
- 로그인 사용자 정보
- 테마 / 다크모드
- 언어 설정
- 알림 카운트
const UserContext = createContext(null);
👉 이건 UI 상태가 아니라 앱 상태다.
6️⃣ 전역 상태가 지옥이 되는 순간
아래 상태들은 전역으로 두는 순간 사고가 난다.
- 모달 열림 여부
- 특정 화면의 검색 조건
- 특정 탭의 선택 상태
이유는 간단하다.
화면이 사라져도
상태는 살아남기 때문
그 결과:
- 이전 화면의 상태가 남아있고
- 새 화면에서 “왜 이 값이?”가 된다
7️⃣ 실무에서 쓰는 최종 판단 체크리스트
상태를 만들기 전에 이 순서로 묻는다.
- 이 컴포넌트만 쓰는가? → 로컬
- 형제/부모/자식이 같이 쓰는가? → 공통 부모
- 화면 여러 개에서 쓰는가? → 상위 페이지
- 앱 전체에서 쓰는가? → 전역
- 서버에서 내려온 데이터인가? → state일 수도 아닐 수도
여기서 4번은 마지막 선택지다.
8️⃣ 기억해야 할 한 문장
이 문장은 React 설계에서 계속 써먹게 된다.
상태는 “가장 적게 필요로 하는 위치”에 둔다
위로 올리는 건 쉽지만
내려오는 건 항상 고통이다
다음 글 예고
다음 글에서는 이걸 이어간다.
👉 “전역 상태는 왜 마지막 카드인가”
- Context / Redux / Zustand를 언제 쓰는지
- 왜 전역 상태는 디버깅을 망가뜨리는지
- 실무에서 전역 상태가 늘어나는 진짜 이유
React 카테고리의 중앙 분기점이 될 글이다.
'framework_library > react' 카테고리의 다른 글
| Context / Zustand / Redux (0) | 2026.01.10 |
|---|---|
| 전역 상태는 왜 마지막 카드인가 (0) | 2026.01.10 |
| useEffect는 왜 위험한가 (0) | 2026.01.09 |
| React가 다시 그리기로 결정하는 정확한 순간들 (0) | 2026.01.09 |
| 상태(State)로 UI를 설계하는 사고방식 (0) | 2025.04.08 |
댓글