IT의 IT 블로그
React 상태 끌어올리기(state lifting)와 props 흐름 정리 본문
리액트를 처음 배우거나, 어느 정도 익숙해진 뒤에도 계속 헷갈리는 개념들이 있습니다.
- 부모 컴포넌트 / 자식 컴포넌트
- props와 state의 역할
- state를 언제 끌어올려야 하는지
- 왜 “올린다(lifting)”고 부르는지
- Context는 언제 써야 하는지
이 글은렌더링 구조와 상태의 ‘소유권’ 이라는 관점으로
이 개념들을 한 번에 정리하는 것이 목표입니다.
1. 부모 컴포넌트란 무엇인가
리액트에서의 정확한 정의
JSX에서 다른 컴포넌트를 직접 렌더링하는 컴포넌트
function Parent() { return <Child />; }
- Parent → 부모 컴포넌트
- Child → 자식 컴포넌트
이 관계의 기준은 오직 JSX 구조입니다.
2. “상위 / 하위”의 기준
다음은 부모·자식 판단 기준이 아닙니다.
- 파일 위치
- 폴더 구조
- import 순서
- 컴포넌트 이름
부모/자식의 기준은 단 하나입니다.
“누가 JSX에서 누구를 렌더링했는가”
3. JSX 트리로 보는 상위/ 하위 기준
function A() {
return <B />;
}
function B() {
return <C />;
}
function C() {
return <div>Hello</div>;
}
JSX 트리 구조는 다음과 같습니다.
A (부모)
└─ B (자식, 동시에 C의 부모)
└─ C (자식)
- A는 C를 직접 import하지 않아도
- JSX 트리 기준으로는 C의 조상 컴포넌트
**부모 컴포넌트란 “렌더링하는 위치”**로 이해하면 됩니다.
4. 컴포넌트 이름은 반드시 대문자로 시작
React는 JSX에서
- 소문자 시작 → HTML 태그
- 대문자 시작 → 사용자 정의 컴포넌트
로 인식합니다.
위 코드는 컴포넌트가 아니라
<nameButton>이라는 알 수 없는 DOM 태그로 처리됩니다.
컴포넌트는 반드시 대문자로 시작
5. state와 props 기본 개념
state란?
컴포넌트가 “소유”하고 관리하는 데이터
- 바뀔 수 있음
- UI 변경의 원인
- useState로 선언
const [count, setCount] = useState<number>(0);
사실, React의 useState는 state 값 하나만 반환하는 게 아니라, 배열을 반환합니다.
위의 코드는 내부적으로 이런 형태입니다.
const stateArray = useState<number>(0);
// stateArray[0] → 현재 상태 값
// stateArray[1] → 상태를 변경하는 함수
그래서 우리는 구조 분해 할당을 사용해
- 첫 번째 값 → state
- 두 번째 값 → setState 함수
로 나누어 쓰는 것입니다.
왜 배열일까?
React는 반환 순서가 보장되는 구조를 필요로 합니다.
- 객체 → 키 이름 충돌, 구조 변경 위험
- 배열 → 항상 [값, 변경 함수] 순서 고정
그래서 useState는 배열을 반환하도록 설계되었습니다.
한 줄 정리
useState는
[현재 상태 값, 상태를 변경하는 함수] 형태의 배열을 반환한다.
props란?
부모가 자식에게 “전달”하는 데이터
- 읽기 전용
- 자식은 수정 불가
- 부모가 값의 주인
type HelloProps = {
name: string;
};
function Hello({ name }: HelloProps) {
return <h2>Hello, {name}</h2>;
}
즉, 아래와 같습니다
- state = 관리
- props = 전달
- 컴포넌트 = 표현
6. 그냥 props로 내려주는 경우 (state 이동 없는 상황)
상황
부모가 이미 데이터를 가지고 있고,
자식들은 그걸 표시만 한다.
import { useState } from "react";
type NameProps = {
name: string;
};
function Hello({ name }: NameProps) {
return <h2>Hello, {name}</h2>;
}
function Badge({ name }: NameProps) {
return <p>Badge: {name}</p>;
}
export default function App() {
const [name] = useState<string>("RIT");
return (
<div>
<Hello name={name} />
<Badge name={name} />
</div>
);
}
- state는 처음부터 App이 소유
- 자식은 props로 받아서 표시만 함
이것은 상태 끌어올리기가 (state lifting) 아닙니다.
7. 상태 끌어올리기(lifting state up)
문제 상황 (Before)
function NameDisplay() {
return <h2>현재 이름: ???</h2>;
}
function NameEditor() {
const [name, setName] = useState<string>("RIT");
return (
<input
value={name}
onChange={(e) => setName(e.target.value)}
/>
);
}
export default function App() {
return (
<>
<NameDisplay />
<NameEditor />
</>
);
}
- name은 NameEditor만 알고 있음
- 형제 컴포넌트와 공유 불가
- 같은 의미의 상태가 분리됨
해결 (After: state를 부모로 이동)
type NameDisplayProps = {
name: string;
};
function NameDisplay({ name }: NameDisplayProps) {
return <h2>현재 이름: {name}</h2>;
}
type NameEditorProps = {
name: string;
onChangeName: (next: string) => void;
};
function NameEditor({ name, onChangeName }: NameEditorProps) {
return (
<input
value={name}
onChange={(e) => onChangeName(e.target.value)}
/>
);
}
export default function App() {
const [name, setName] = useState<string>("RIT");
return (
<>
<NameDisplay name={name} />
<NameEditor name={name} onChangeName={setName} />
</>
);
}
이 방식의 핵심은 state의 소유권을 공통 부모로 이동시킨 것입니다.
기존에는 name 상태를 NameEditor만 알고 있었기 때문에,
형제 컴포넌트인 NameDisplay는 같은 데이터를 사용할 수 없었습니다.
이를 해결하기 위해 다음과 같이 구조를 변경했습니다.
- name 상태를 공통 부모 컴포넌트(App) 로 이동하고
- 부모가 해당 상태를 한 곳에서만 관리하는 기준값으로 소유하며
- 자식 컴포넌트들은
- 값은 props로 전달받아 사용하고
- 변경은 콜백 함수를 통해 부모에게 “요청”하도록 구조를 변경했습니다.
const [name, setName] = useState<string>("RIT");
이제 App이 상태의 주인이 되고,
- NameDisplay는 name을 읽기 전용으로 사용
- NameEditor는 입력 이벤트 발생 시
setName을 호출해 부모의 상태 변경을 요청
하게 됩니다.
이 구조의 장점
- 같은 의미의 상태가 한 곳에서만 관리됨
- 형제 컴포넌트 간 데이터 불일치 문제 해결
- 데이터 흐름이 항상
부모 → 자식 (props)
자식 → 부모 (callback)
형태로 유지됨
즉, 이 구조가 바로
React가 의도한 단방향 데이터 흐름을 따르는 설계입니다.
8. 왜 “올린다(lifting)”고 부를까?
헷갈리는 이유는 대부분 이것입니다.
“props는 아래로 내려가는데 왜 올린다고 하지?”
lifting은 렌더링 방향이 아니라
state의 ‘소유 위치’ 기준입니다.
state의 위치가 위로 이동했기 때문에
lift(끌어올리다) 라고 부릅니다.
9. state lifting의 정확한 정의
같은 의미의 상태를 여러 컴포넌트가 필요로 할 때,
그 상태를 가장 가까운 공통 부모가 소유하도록 위치를 옮기는 설계 패턴
10. 언제 state lifting을 하면 안 되는가
① 한 컴포넌트 내부 UI 상태
const [isOpen, setIsOpen] = useState<boolean>(false);
- 모달 열림/닫힘
- 드롭다운 토글
- 포커스 여부
공유 필요 없음 → 그 컴포넌트가 주인
② 부모가 의미를 모르는 상태
<Input />
- 부모는 배치만 함
- 값의 의미, 로직에 관심 없음
이런 상태까지 올리면 설계가 망가집니다
11. React는 단방향 데이터 흐름이다
리액트의 핵심 원칙 중 하나는 이것입니다.
데이터는 항상 위 → 아래로만 흐른다
- 부모 → 자식 (props)
- 자식 → 부모 (직접 전달 하지않고 콜백으로 요청)
onChangeName(nextValue);
자식은 부모의 state를 직접 바꾸지 못하고,
부모가 내려준 함수만 호출합니다.
이 단방향 구조 덕분에
- 데이터 흐름이 예측 가능해지고
- 디버깅이 쉬워지며
- UI와 상태의 관계가 명확해집니다
12. Context는 언제 쓰는가
기준 한 줄
공유 범위가 넓어질수록 Context
공유 범위가 좁으면 lifting
Context가 어울리는 예
- 로그인 사용자 정보
- 테마 (다크/라이트)
- 언어 설정
최종 정리 한 문장
부모 컴포넌트는 렌더링하는 위치이고,
state lifting은 상태의 소유권을 올바른 위치로 옮기는 설계 판단이다.
이상으로 부모 컴포넌트, props, 그리고 state lifting의 개념을
렌더링 구조와 상태 소유권 관점에서 정리해보았습니다.
'React • TypeScript 학습 기록' 카테고리의 다른 글
| 리액트 렌더링 과정 정리 (1) | 2026.01.12 |
|---|