Blog
props drilling이 생겼던 구조와 당시 선택
서론#
컴포넌트 구조가 단순할 때는
props를 내려주는 방식이 크게 문제되지 않는다.
하지만 상태를 상위로 끌어올리기 시작하면
자연스럽게 props drilling이 발생한다.
이 글은
props drilling이 실제로 문제가 되기 시작한 구조와,
그 시점에서 어떤 선택을 했는지에 대한 기록이다.
props drilling이 생겼던 구조#
게시글 목록 페이지에서
필터 상태를 상위 컴포넌트에서 관리하고 있었다.
구조는 대략 다음과 같았다.
<Page>
<ListContainer>
<ListHeader />
<PostList>
<PostItem />
</PostList>
</ListContainer>
</Page>
필터 상태는 Page에 있었고,
실제로 필요한 곳은 ListHeader와 PostList였다.
그래서 props는 이렇게 흘러갔다.
<Page>
<ListContainer filter={filter} onChangeFilter={setFilter}>
<ListHeader filter={filter} onChangeFilter={setFilter} />
<PostList filter={filter} />
</ListContainer>
</Page>
문제는 ListContainer였다.
이 컴포넌트는
해당 props를 사용하지도 않으면서,
구조를 유지하기 위해 전달만 하고 있었다.
이 구조가 불편해진 지점#
props drilling 자체가 나쁜 것은 아니다.
하지만 이 구조에서는
다음과 같은 문제가 동시에 나타났다.
- 중간 컴포넌트의 책임이 불명확해짐
- props가 늘어날수록 컴포넌트 시그니처가 복잡해짐
- 구조를 이해하려면 props 흐름을 끝까지 따라가야 함
특히 구조를 수정하거나
컴포넌트를 재사용하려고 할 때
불필요한 의존성이 발목을 잡았다.
Context API를 고려하게 된 지점#
이후 필터 상태를 사용하는 컴포넌트가 늘어나면서
상태의 성격이 분명해졌다.
- 동일한 페이지 내 여러 컴포넌트에서 사용
- props로 내려보내기에는 경로가 너무 김
- 페이지를 벗어나면 필요 없음
이 시점에서
Context API가 자연스럽게 선택지로 올라왔다.
Context API 적용 예시 (간단 버전)#
// FilterContext.tsx
import { createContext, useContext, useState } from "react";
const FilterContext = createContext<{
filter: string;
setFilter: (value: string) => void;
} | null>(null);
export const FilterProvider = ({ children }: { children: React.ReactNode }) => {
const [filter, setFilter] = useState("all");
return <FilterContext.Provider value={{ filter, setFilter }}>{children}</FilterContext.Provider>;
};
export const useFilter = () => {
const context = useContext(FilterContext);
if (!context) throw new Error("FilterProvider 내부에서만 사용 가능합니다.");
return context;
};
// Page.tsx
<FilterProvider>
<ListContainer />
</FilterProvider>
// ListHeader.tsx
const { filter, setFilter } = useFilter();
Context를 써도 해결되지 않는 것#
Context를 도입했다고 해서
모든 문제가 사라지는 것은 아니었다.
- Context는 전역 상태가 아니다
- 남용하면 의존성이 보이지 않게 숨겨진다
- 렌더링 범위에 대한 고려가 필요하다
그래서 Context는
props drilling을 없애기 위한 도구라기보다,
상태의 범위가 명확해졌을 때 사용하는 수단에 가까웠다.
정리#
props drilling은
구조가 잘못됐다는 신호라기보다,
상태의 위치를 다시 고민하라는 힌트에 가깝다.
그래서 중요한 것은
"props를 없애는 것"이 아니라,
- 이 상태는 어디까지 영향을 미치는지
- 정말 공통 상태인지
- 페이지를 벗어나도 필요한지
를 먼저 판단하는 것이다.
Context API는
그 판단 이후에 등장하는 선택지 중 하나일 뿐이다.
관련 게시글
8개
JSX가 무엇인지 정리하기
JSX가 무엇인지 개념부터 정리합니다. HTML처럼 보이지만 JavaScript 문법 확장이라는 점과, 빌드 과정에서 함수 호출로 변환되는 구조를 중심으로 설명합니다.
메모이제이션이란 무엇인가
메모이제이션이 무엇인지 개념부터 정리합니다. 계산 최적화 기법으로서의 메모이제이션이 어떤 전제에서 성립하는지, 그리고 언제 의미를 가지는지를 중심으로 설명합니다
자주 사용하는 리액트 훅 (2)
React에서 구조와 렌더링 타이밍을 다루기 시작하면서 사용하게 된 훅들을 정리합니다. useLayoutEffect, useContext, 커스텀 훅이 어떤 상황에서 유용했는지를 중심으로 기록합니다.
자주 사용하는 리액트 훅 (1)
React에서 가장 자주 사용하는 훅인 useState, useEffect, useRef를 정리합니다. 각 훅이 무엇을 위한 도구인지와 어떤 상황에서 유용한지를 중심으로 설명합니다.
리액트에서 key를 사용하는 이유
React에서 리스트를 렌더링할 때 key가 왜 필수인지 정리합니다. React의 렌더링 방식과 index를 key로 사용할 때 발생하는 문제를 중심으로, 안정적인 key를 선택하는 기준을 기록합니다.
useState로 상태를 관리하다가 불편해지기 시작한 지점
React에서 useState로 상태를 관리하던 중, 페이지 이동·새로고침·뒤로가기 상황에서 상태가 유지되지 않았던 문제를 정리합니다.
clsx 다음에 tw-merge가 필요한 이유
Tailwind CSS에서 clsx로 조건부 클래스를 합친 뒤, tw-merge로 클래스 충돌을 정리하는 이유를 정리합니다. clsx와 tw-merge를 함께 사용하는 흐름과 cn 유틸 패턴을 설명합니다.
clsx / classnames를 언제, 왜 사용하는가
Tailwind CSS를 사용하며 조건부 클래스가 늘어날 때, clsx와 classnames를 언제, 왜 사용하게 되는지 정리합니다. 문자열 기반 클래스 처리의 한계와 정리 기준을 학습 관점에서 설명합니다.