1. 상태 끌어올리기란 무엇인가?
리액트를 사용하다 보면 여러 컴포넌트가 같은 데이터를 공유해야 하는 상황이 자주 생깁니다.
예를 들어,
- 형제 컴포넌트 간에 입력값을 공유하거나
- 상위 컴포넌트에서 하위 여러 컴포넌트로 동일한 데이터를 전달해야 할 때가 그렇습니다.
이때 가장 기본적인 접근법이 바로 상태 끌어올리기 (Lifting State Up) 입니다.
즉, 공통으로 사용하는 데이터를 상위 컴포넌트의 state로 옮기고,
그 데이터를 props로 하위 컴포넌트에 전달하는 방식입니다.
2. 왜 상태를 끌어올려야 하는가?
리액트의 데이터 흐름은 단방향(one-way) 입니다.
즉, 부모 → 자식으로만 데이터가 전달됩니다.
하지만 다음과 같은 상황을 생각해봅시다.
function TemperatureInput({ label, value, onChange }) {
return (
<div>
<label>{label}</label>
<input type="number" value={value} onChange={onChange} />
</div>
);
}
섭씨(°C)와 화씨(°F) 두 입력창이 있고,
하나를 바꾸면 다른 하나도 자동으로 바뀌어야 한다면
두 입력창이 서로의 값을 알아야 합니다.
하지만 형제 컴포넌트끼리는 직접 데이터를 주고받을 수 없습니다.
→ 그래서 상태를 공통 부모 컴포넌트로 끌어올리는 것이 필요합니다.
3. 예제 – 섭씨와 화씨 변환기
아래는 상태 끌어올리기의 전형적인 예시입니다.
import React, { useState } from 'react';
function TemperatureInput({ label, value, onChange }) {
return (
<div style={{ marginBottom: '10px' }}>
<label>{label}</label>
<input type="number" value={value} onChange={(e) => onChange(e.target.value)} />
</div>
);
}
function toCelsius(fahrenheit) {
return ((fahrenheit - 32) * 5) / 9;
}
function toFahrenheit(celsius) {
return (celsius * 9) / 5 + 32;
}
function Calculator() {
const [temperature, setTemperature] = useState('');
const [scale, setScale] = useState('c'); // c or f
const handleCelsiusChange = (value) => {
setScale('c');
setTemperature(value);
};
const handleFahrenheitChange = (value) => {
setScale('f');
setTemperature(value);
};
const celsius = scale === 'f' ? toCelsius(temperature) : temperature;
const fahrenheit = scale === 'c' ? toFahrenheit(temperature) : temperature;
return (
<div>
<h3>온도 변환기</h3>
<TemperatureInput
label="섭씨 (°C): "
value={celsius}
onChange={handleCelsiusChange}
/>
<TemperatureInput
label="화씨 (°F): "
value={fahrenheit}
onChange={handleFahrenheitChange}
/>
</div>
);
}
export default Calculator;
이 예제에서 Calculator 컴포넌트는
섭씨와 화씨 입력값을 모두 관리하며,
각 입력창은 props를 통해 부모의 상태를 공유합니다.
결과적으로 두 입력 필드는 서로 동기화되어 작동합니다.
4. 상태 끌어올리기의 장점
- 데이터 일관성 유지
모든 데이터의 근원이 부모 컴포넌트 하나이므로,
서로 다른 하위 컴포넌트들이 항상 동일한 정보를 바라보게 됩니다. - 버그 발생 가능성 감소
상태를 분산시키지 않아 데이터 불일치 문제가 줄어듭니다. - 컴포넌트 간 재사용성 향상
하위 컴포넌트는 단순히 “값을 보여주는 역할”만 하므로,
다른 곳에서도 재사용하기 쉽습니다.
5. 하지만 한계도 있다
상태를 너무 많이 끌어올리면,
부모 컴포넌트가 지나치게 많은 상태를 관리하게 되어 복잡해집니다.
이 문제를 해결하기 위해 등장한 것이 Context API입니다.
6. Context API란 무엇인가?
Context API는 리액트에서 전역적으로 데이터를 관리하고,
여러 컴포넌트에 손쉽게 전달하기 위한 기능입니다.
일반적으로 데이터를 자식에게 전달할 때는 props를 사용하지만,
중간에 여러 단계의 컴포넌트가 껴 있으면 “prop drilling” 문제가 생깁니다.
prop drilling
: 데이터를 전달하기 위해 불필요하게 여러 컴포넌트를 거쳐야 하는 현상
Context를 사용하면 이러한 문제를 해결할 수 있습니다.
7. Context API 기본 구조
Context는 세 가지 요소로 구성됩니다.
1️⃣ Context 생성
2️⃣ Provider (공급자)
3️⃣ Consumer (소비자)
import React, { createContext, useContext, useState } from 'react';
// 1. Context 생성
const ThemeContext = createContext();
// 2. Provider로 감싸기
function App() {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<Toolbar />
</ThemeContext.Provider>
);
}
// 3. Consumer: useContext 훅 사용
function Toolbar() {
return (
<div>
<ThemeButton />
</div>
);
}
function ThemeButton() {
const { theme, setTheme } = useContext(ThemeContext);
return (
<button
onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
style={{
backgroundColor: theme === 'light' ? '#eee' : '#333',
color: theme === 'light' ? '#333' : '#eee',
}}
>
{theme === 'light' ? '라이트 모드' : '다크 모드'}
</button>
);
}
이 예제에서 ThemeContext는 전역적인 테마 정보를 관리합니다.
Provider는 데이터를 하위 컴포넌트에 공급하고,
하위 컴포넌트들은 useContext()를 통해 언제든 이 데이터를 읽을 수 있습니다.
8. Context 사용 시 주의점
- 불필요한 리렌더링 주의
Provider의 값이 바뀌면,
그 안의 모든 하위 컴포넌트가 리렌더링됩니다.
큰 트리 구조에서는 성능 저하가 발생할 수 있습니다. - Context 남용 금지
전역 상태를 관리한다고 해서 모든 상태를 Context로 두면,
코드 복잡도가 오히려 증가합니다.
→ 자주 바뀌는 상태는 Recoil, Redux, Zustand 같은 별도 상태 관리 라이브러리를 고려해야 합니다.
9. 상태 끌어올리기 vs Context API
구분 상태 끌어올리기 Context API
| 목적 | 여러 자식이 같은 데이터를 공유 | 전역적으로 데이터 관리 |
| 구조 | 상위 → 하위로 props 전달 | Provider → Consumer 구조 |
| 장점 | 단순하고 명시적 | prop drilling 제거 |
| 단점 | 부모가 복잡해질 수 있음 | 전역 리렌더링 위험 |
| 사용 예시 | 폼 입력값, 형제 컴포넌트 공유 | 테마, 로그인 상태, 언어 설정 |
10. 마무리
이번 강의에서는 **상태 끌어올리기(Lifting State Up)**과
Context API의 기본 개념을 다뤘습니다.
핵심 요약:
- 형제 컴포넌트 간 데이터 공유는 “상위 컴포넌트로 상태를 끌어올려 해결”한다.
- 데이터 전달이 깊어질수록 Context API로 관리하는 것이 효율적이다.
- 단, Context는 전역 상태로 사용할 때만 도입해야 하며 남용은 금물이다.
다음 강의에서는 **리액트의 컴포넌트 최적화(React.memo, useMemo, useCallback)**를 통해
렌더링 성능을 개선하는 방법을 살펴보겠습니다.
'frontend > react' 카테고리의 다른 글
| [React] React.js 강좌 12. React Router 완벽 가이드 – 라우팅의 기본부터 동적 라우팅까지 (0) | 2025.11.10 |
|---|---|
| [React] React.js 강좌 11. 리렌더링 최적화 – React.memo, useMemo, useCallback 완벽 정리 (0) | 2025.11.10 |
| [React] React.js 강좌 9. Hooks 기본 – useState와 useEffect 완벽 이해 (0) | 2025.11.10 |
| [React] React.js 강좌 3. Fragments와 컴포넌트 구조화 (1) | 2025.11.10 |
| [React] React.js 강좌 2. Props와 State 완벽 정리 (0) | 2025.11.10 |