3-1편: State Lifting과 Prop Drilling 회피 전략

JIIJIIJ·2025년 10월 29일

React

목록 보기
36/38
post-thumbnail

props가 깊어질수록 왜 복잡해질까?

React의 단방향 데이터 흐름은 이론적으로는 단순하다.

부모가 데이터를 관리하고, 자식은 props로 전달받아 화면을 그린다.

하지만 프로젝트가 커지고 컴포넌트 트리가 깊어지면 상황이 달라진다.

“React의 단방향 통신은 좋다는데,

실제로 써보면 props만 계속 넘기느라 정신이 없다…”

state를 부모로 끌어올릴수록, 그 데이터를 필요로 하지 않는 중간 컴포넌트들까지 props를 단순 전달만 하는 파이프 역할을 하게 된다.

데이터는 여전히 위에서 아래로 흐르지만, 파이프가 너무 많아지면 결국 물길을 관리하기가 더 어려워진다.

이게 바로 많은 React 개발자가 “prop drilling”이라 부르는 문제다.


State Lifting이란 무엇인가

State Lifting (상태 끌어올리기)

여러 하위 컴포넌트가 공유해야 하는 데이터를 공통 부모로 올려 관리하는 패턴.

// Before: 각자 state 관리
function ChildA() {
  const [value, setValue] = useState('');
  return <input value={value} onChange={(e) => setValue(e.target.value)} />;
}

function ChildB() {
  const [value, setValue] = useState('');
  return <p>{value}</p>;
}

// After: 부모로 상태를 올려 통합 관리
function Parent() {
  const [value, setValue] = useState('');
  return (
    <>
      <ChildA value={value} onChange={setValue} />
      <ChildB value={value} />
    </>
  );
}

React는 “데이터의 진실은 한 곳에 있다”는 단방향 원칙을 유지하면서, 여러 하위 컴포넌트가 동일한 정보를 예측 가능하게 바라보도록 한다.

📖 참고 | react.dev – Lifting State Up


문제의 확장 – Prop Drilling 지옥

State Lifting 자체는 좋지만, 트리가 깊어질수록 props 전달이 아래로 연쇄된다.

<App>
  <Layout>
    <Sidebar>
      <UserPanel user={user} onChangeUser={setUser} />
    </Sidebar>
  </Layout>
</App>

중간 컴포넌트(Layout, Sidebar)는 user를 직접 쓰지도 않지만 전달만 한다.

이 구조에서는 유지보수가 어렵고 의존성 파악도 힘들다.

마치 사장의 지시가 팀장을 거쳐 팀원에게 전달되는 긴 보고 체계처럼, 전달 경로가 길어질수록 어디서 오류가 났는지 추적하기가 어려워진다.

📖 참고 | react.dev – Thinking in React


해결 전략 – Prop Drilling 줄이는 세 가지 방법

좋아요 — 아래는 요청하신 내용을 완전한 Markdown 표 형식으로 정리한 버전입니다.
Velog, Tistory, GitHub 블로그 어디에 붙여도 깨지지 않습니다.

전략개념React 내장 기능주의 사항
1. 상태 위치 재조정상태를 공유하는 가장 가까운 공통 부모로 이동State Lifting (패턴)너무 상위로 끌어올리면 재렌더링 비용이 증가할 수 있다.
2. Context API 활용중간 컴포넌트를 건너뛰고 필요한 곳에 직접 전달createContext, useContext전역처럼 남용되면 다시 복잡도가 증가할 수 있다.
3. 상태관리 라이브러리React 외부의 전역 스토어에서 상태 관리Redux, Zustand, Recoil 등학습 및 설계 비용이 존재한다.

이 세 가지 방법은 “데이터는 한 방향으로 흐른다”는 React 철학을 유지하면서,

현실적인 유지보수 문제를 해결하기 위한 도구들이다.

그중에서도 가장 기본이자 내장된 방법은 Context API다.

📖 참고 | react.dev – Sharing State Between Components


Context API로 개선하기

// Context 생성
const UserContext = createContext();

// 상위 컴포넌트
function App() {
  const [user, setUser] = useState('Jin');
  return (
    <UserContext.Provider value={{ user, setUser }}>
      <Layout />
    </UserContext.Provider>
  );
}

// 깊은 하위 컴포넌트
function UserPanel() {
  const { user, setUser } = useContext(UserContext);
  return (
    <div>
      <p>{user}</p>
      <button onClick={() => setUser('JaeHwan')}>Change</button>
    </div>
  );
}

이제 UserPanel은 props 없이도 데이터를 접근한다.

중간 단계의 불필요한 전달이 사라지고, 여전히 단방향 흐름은 유지된다.

📖 참고 | react.dev – Passing Data Deeply with Context


실무에서의 판단 기준

단방향 철학을 유지하면서도 과도한 추상화를 피하려면,

“Prop Drilling을 어디까지 허용할 것인가?”를 명확히 정의해야 한다.

  • 전달 깊이가 2~3단계 이하라면 그대로 props로 전달해도 무방하다.
  • 중간 컴포넌트가 해당 데이터를 직접 사용하지 않는다면 Context로 대체하라.
  • 빈번히 변하는 데이터는 너무 상위로 끌어올리면 오히려 리렌더링 비용이 커진다.

즉, “모든 것을 Context로 전역화할 필요는 없다”는 점이 핵심이다.

상황에 따라 Lifting과 Context를 병행해야 한다.

📖 참고 | react.dev – Scaling Up with Reducer and Context


심화 섹션 A – 언제 Prop Drilling을 허용해도 될까?

판단 기준설명
깊이 2~3단계 이하props로 직접 전달해도 충분함
중간 컴포넌트가 직접 사용Context 대신 props 유지
렌더링 비용 높음Context 도입 전 비용 고려

📋 체크리스트 예시

  • 상태를 끌어올릴 공통 부모가 명확한가?
  • Context/Store 도입까지 고려되는가, 아니면 단순 prop 전달로 충분한가?
  • 너무 상위로 끌어올리면 오히려 리렌더링 부담이 높아지지 않는가?

📖 참고 | react.dev – Keeping Components Pure (컴포넌트 설계 원칙)


심화 섹션 B – Context 혹은 Store 사용 시 주의사항과 최신 트렌드

Context는 강력하지만, 남용하면 전체 컴포넌트가 동시에 리렌더링될 위험이 있다.

Provider의 value 객체가 매번 새로 생성되면 하위 모든 컴포넌트가 다시 렌더링되기 때문이다.

이를 막으려면 useMemo를 사용해 value를 메모이제이션하거나, Context를 여러 개로 분리해 설계하는 것이 좋다.

JavaScript

const value = useMemo(() => ({ user, setUser }), [user]);

이렇게 하면 React가 이전 값을 재사용해 불필요한 리렌더링을 줄인다.

🔄 최신 버전 기준 변화 (React 19 이후)

React Compiler가 정식 도입되면서, 대부분의 일반 컴포넌트에서

useMemo / useCallback / React.memo 같은 수동 최적화 훅을 사용하지 않아도 된다는

내용이 공식 문서에 반영되었습니다. (react.dev/learn/react-compiler)

  • React 19 이상에서 컴파일러 기반 자동 최적화가 강화됨
  • 함수 재생성, 참조 안정성 등은 자동으로 감지 및 캐싱 처리
  • 다만, 컴파일러가 활성화되지 않은 프로젝트에서는 여전히 useMemo가 필요함

✅ 결론: 이제는 “기본적으로는 사용하지 않고, 성능 문제가 확인되면 적용”하는 방식이 권장됩니다.

📖 참고 | react.dev – useMemoReact Compiler


심화 섹션 C – 성능 측정·리팩토링 전략

단계설명
1️⃣React.Profiler로 렌더링 횟수를 측정한다.
2️⃣전달 깊이를 시각화한다.
3️⃣상태 위치를 조정하거나 Context를 도입한다.
4️⃣리렌더링 횟수 감소 여부를 확인한다.

“Prop Drilling을 제거한다”가 목표가 아니라, “불필요한 렌더링을 줄인다”가 진짜 목적이다.

📖 참고 | react.dev – Profiler API


결론

React의 단방향 통신 철학을 설계 단계에서 구현한다는 것은,

“단순히 위에서 아래로 데이터가 흐른다”는 구조적인 말이 아니다.

그건 “변화의 책임을 명확히 두고, 데이터의 출처를 예측 가능하게 만든다”는 의미다.

“상태의 단일 진실” 유지, “데이터는 위에서 아래로” 흐르는 구조, “전달용 파이프” 최소화

이 세 가지는 단방향 통신을 실제 팀 개발 맥락에서 생존 가능하게 만드는 설계 규칙이다.

실무 가치

가치설명
예측 가능성 (Predictability)데이터 흐름이 단순할수록 버그가 줄고, 디버깅이 쉬워진다.
유지보수성 (Maintainability)전달 체인이 짧을수록 코드 변경의 비용이 내려간다.
협업 효율성 (Team Efficiency)팀원이 “데이터는 위에서만 내려온다”는 설계 규칙만 알면 참여하기 쉬워진다.

📖 참고 | react.dev – Managing State (전반적인 상태 관리)

profile
다크모드가 보기 좋아요

0개의 댓글