보통은 props를 통하여 부모 컴포넌트에서 자식 컴포넌트까지 정보를 전달할 거예요. 하지만 만약 props를 중간에 있는 많은 컴포넌트를 통해 전달한다면 또는 앱 안에 있는 여러 컴포넌트에 동일한 정보가 필요하다면 props를 전달하는 것은 너무 구구절절하고 불편해요. 컨텍스트는 명시적으로 props를 전달하지 않아도 부모 컴포넌트가 깊이 상관 없이 트리 안에 있는 어떤 컴포넌트에게 정보를 전달할 수 있도록 만들어줘요.
이 페이지에서는
- "props drilling"이 무엇인지
- 어떻게 반복적인 prop 전달을 컨텍스트로 대체할 수 있는지
- 컨텍스트의 일반적인 사용 사례
- 컨텍스트를 대체할 수 있는 일반적인 방법들
을 알아볼 거예요.
The problem with passing props | props를 전달하면 생기는 문제점
props를 전달하는 것은 UI 트리를 통해 명시적으로 정보를 사용할 컴포넌트로 데이터를 보내는 좋은 방법이에요.
그러나 트리의 깊은 곳까지 prop을 전달해야하거나 여러 컴포넌트에서 동일한 prop이 필요하다면 props를 전달하는 것은 너무 구구절절하고 불편해요. 가장 가까운 공통 조상은 데이터가 필요한 컴포넌트에서 멀리 떨어져 있을 수도 있고 그렇게 높은 곳으로 상태를 끌어올리면 "prop drilling"이라고 불리는 상황을 초래할 수 있어요.
상태 끌어올리기
Prop drilling


만약 트리에서 데이터가 필요한 컴포넌트에 props로 전달하지 않고 데이터를 "순간이동" 시키면 정말 좋지 않을까요? 리액트의 컨텍스트를 사용하면 할 수 있어요!
Context: an alternative to passing props | 컨텍스트: props 전달의 대안
컨텍스트는 부모 컴포넌트가 하위에 있는 모든 트리에 데이터를 전달하도록 해줘요. 컨텍스트를 사용하는 방법은 여러가지가 있어요. 그 중 한가지 예시에요. 이 Heading
컴포넌트가 크기를 위해 level
을 받는다고 생각해보세요.
같은 Section
에서 항상 같은 크기를 가지는 여러 헤딩이 필요하다고 생각해볼게요.
현재 여러분은 각각의 <Heading>
에 level
prop을 전달하고 있어요.
<Section>
<Heading level={3}>About</Heading>
<Heading level={3}>Photos</Heading>
<Heading level={3}>Videos</Heading>
</Section>
<Section>
컴포넌트에 level
prop을 전달하고 <Heading>
에서는 해당 prop을 지우는 것이 더 좋을 것 같아요. 이 방법은 같은 섹션에 있는 모든 해딩은 같은 크기를 갖도록 강제할 수 있어요.
<Section level={3}>
<Heading>About</Heading>
<Heading>Photos</Heading>
<Heading>Videos</Heading>
</Section>
그러나 <Heading>
컴포넌트가 가장 가까운 <Section>
의 레벨을 어떻게 알 수 있을까요? 자식 컴포넌트가 트리 위쪽에 있는 어딘가에서 데이터를 "요청"할 수 있는 방법이 필요해요.
props만을 사용해서 이렇게 할 수는 없어요. 컨텍스트가 필요한 타이밍이에요. 아래의 세 단계를 통해 이를 실행할 수 있어요.
- 컨텍스트를 생성하세요. (헤딩의 레벨을 위한 컨텍스트이기 때문에
LevelContext
라고 부를게요.) - 데이터가 필요한 컴포넌트에서 컨텍스트를 사용하세요. (
Heading
은LevelContext
를 사용해요.) - 데이터를 지정하는 컴포넌트에서 컨텍스트를 제공하세요 (
Section
은LevelContext
를 제공해요.)
컨텍스트는 설령 원거리에 있는 부모 컴포넌트더라도 그 안에 있는 데이터를 트리 전체에 제공하도록 만들어줘요.
가까운 자식에서 컨텍스트 사용하기
원거리에 있는 자식에서 컨텍스트 사용하기


Step 1: Create the context | 1단계: 컨텍스트 만들기
먼저, 컨텍스트를 만들어야해요. 파일에서 컨텍스트를 내보내서 컴포넌트가 사용할 수 있도록 만드세요.
createContext
에 필요한 유일한 인자는 _기본 값_이에요. 여기서는 1
은 가장 큰 헤딩의 레벨을 말해요. 하지만 여러분은 (객체를 포함한) 어떤 타입의 값도 전달할 수 있어요. 다음 단계에서 이 기본값의 중요성을 확인할 수 있어요.
Step 2: Use the context | 2단계: 컨텍스트 사용하기
useContext
훅을 리액트에서 가져오고 여러분의 컨텍스트도 가져오세요.
import { useContext } from 'react';
import { LevelContext } from './LevelContext.js';
현재 Heading
컴포넌트는 props에서 level
을 읽어요.
export default function Heading({ level, children }) {
// ...
}
대신 level
prop을 지우고 불러온 컴포넌트인 LevelContext
에서 값을 읽어오세요.
export default function Heading({ children }) {
const level = useContext(LevelContext);
// ...
}
useContext
는 훅이에요. useState
나 useReducer
처럼 여러분은 리액트 컴포넌트 안에서 즉시 (하지만 반복문이나 조건문에서는 안돼요) 훅을 호출해야해요. useContext
는 리액트가 Heading
컴포넌트가 LevelContext
를 읽고 싶어 한다는 사실을 말해줘요.
Heading
컴포넌트는 level
prop을 갖고 있고 아래와 같이 JSX에서 Heading
에 레벨 prop을 더 이상 전달할 필요가 없어요.
<Section>
<Heading level={4}>Sub-sub-heading</Heading>
<Heading level={4}>Sub-sub-heading</Heading>
<Heading level={4}>Sub-sub-heading</Heading>
</Section>
Section
이 대신 이 정보를 받도록 JSX를 업데이트 하세요.
<Section level={4}>
<Heading>Sub-sub-heading</Heading>
<Heading>Sub-sub-heading</Heading>
<Heading>Sub-sub-heading</Heading>
</Section>
다음은 여러분이 동작하도록 시도한 마크업이에요.
이 예시는 아직 그닥 잘 동작하지는 않는다는 점을 기억하세요! 설령 컨텍스트를 사용하더라도 아직 제공하지 않았기 떄문에모든 헤딩은 동일한 사이즈를 가지고 있어요. 리액트는 어디서 가져와야하는지를 몰라요!
만약 컨텍스트를 제공하지 않는다면 리액트는 이전 단계에서 지정한 기본 값을 사용할 거예요. 이 예시에서 createContext
의 첫 번째 인자가 1
이기 때문에 useContext(LevelContext)
는 1
을 반환하고 모든 헤딩은 <h1>
으로 설정돼요. 이 문제를 각 Section
에서 고유 컨텍스트를 제공하여 해결해볼게요.
Step 3: Provide the context | 3단계: 컨텍스트 제공하기
Section
컴포넌트는 현재 자식을 렌더링해요.
export default function Section({ children }) {
return (
<section className="section">
{children}
</section>
);
}
LeveContext
를 여기에 제공하려면 컨텍스트 프로바이더로 자식을 감싸세요.
import { LevelContext } from './LevelContext.js';
export default function Section({ level, children }) {
return (
<section className="section">
<LevelContext.Provider value={level}>
{children}
</LevelContext.Provider>
</section>
);
}
이는 리액트에게 이렇게 말하는 것과 같아요. "만약 이 <Section>
안의 어떤 컴포넌트가 LevelContext
를 요청한다면 이 level
을 그들에게 주세요." 컴포넌트는 UI 트리 상위에서 가장 가까운 <LevelContext.Provider>
의 값을 사용할 거예요.
기존 코드와 동일한 결과를 추출하지만 level
prop을 각 Heading
컴포넌트에 전달할 필요는 없어요. 대신. 가장 가까운 상위 Section
에 요청해서 헤딩 레벨을 찾아내요.
level
prop을<Section>
에 전달하세요.Section
은 자식을<LevelContext.Provider value={level}>
로 감싸세요.Heading
은useContext(LevelContext)
를 사용하여 위에 있는 가장 가까운LevelContext
의 값을 요청해요.
Using and providing context from the same component | 동일한 컴포넌트에서 컨텍스트 사용하고 제공하기
현재는 각 섹션의 level
을 손수 지정해줘야해요.
export default function Page() {
return (
<Section level={1}>
...
<Section level={2}>
...
<Section level={3}>
...
컨텍스트가 상위 컴포넌트에서 정보를 읽도록 만들어주기 때문에 각 Section
은 level
을 위의 Section
에서 읽을 수 있고 level + 1
을 자동적으로 아래로 전달할 수 있어요. 아래와 같이 작성하면 돼요.
import { useContext } from 'react';
import { LevelContext } from './LevelContext.js';
export default function Section({ children }) {
const level = useContext(LevelContext);
return (
<section className="section">
<LevelContext.Provider value={level + 1}>
{children}
</LevelContext.Provider>
</section>
);
}
이렇게 바꾸면 level
prop을 <Section>
이나 <Heading>
에 전달할 필요가 없어요.
Heading
과 Section
은 모두 그들이 어느 정도의 "깊이"를 가지는지를 찾기 위해 LevelContext
를 읽어요. 그리고 Section
은 LevelContext
로 자식을 감싸서 그 안에 있는 어떤 것이든 "더 깊은" 레ㅍ벨을 갖도록 지정해줘요.
노트
헤딩 레벨이 시각적으로 중첩된 컴포넌트가 컨텍스트를 어떻게 오버라이드하는지를 보여주기 때문에 헤딩 레벨을 사용했어요. 하지만 컨텍스트는 다른 수많은 곳에서도 유용하게 사용돼요. 현재 컬러 테마, 현재 로그인한 사용자 등 전체 서브트리에서 필요한 어떤 정보를 전달할 수 있어요.
Context passes through intermediate components | 중간 컴포넌트를 통해 컨텍스트 전달하기
컨텍스트를 제공하는 컴포넌트와 사용하는 컴포넌트 사이에 원하는만큼 많은 컴포넌트를 추가할 수 있어요. <div>
와 같은 내장 컴포넌트나 직접 만든 컴포넌트도 포함해요
.
이 예시에서는 (점선 테두리를 가진) 동일한 Post
컴포넌트가 두 개의 다른 중첩 레벨에서 렌더링돼요. 그 안에 있는 <Heading>
은 자동적으로 가장 가까운 <Section>
에서 레벨을 가져온다는 점을 주목하세요.
이 코드를 동작시키려고 어떤 특별한 짓도 하지 마세요. Section
은 내부의 트리에서 컨텍스트를 지정하기 때문에 <Heading>
을 어디에나 추가할 수 있고 올바른 크기를 갖고 있을 거예요. 위의 샌드박스에서 이를 시도해보세요!
컨텍스트는 "주변 환경에 적응하는" 컴포넌트를 만들도록 해주고 이것들이 어디에서 렌더링 되는지(다른 말로 하면 어떤 컨텍스트에서 렌더링 되는지) 에 따라 다르게 보여줘요.
컨텍스트의 동작 방법은 CSS 속성 상속을 상기시켜요. CSS에서 여러분은 color: blue
를 <div>
에서 지정할 수 있고 다른 DOM 노드가 중간에서 color: green
으로 오버라이드하지 않는다면 깊이와 상관 없이 어느 DOM 노드에서나 해당 색상을 상속할 수 있어요. 이와 비슷하게 리액트에서도 위에서 온 컨텍스트를 오버라이드하는 유일한 방법은 자식을 다른 값을 가지는 컨텍스트 프로바이더로 감싸는 거예요.
CSS에서 coilor
와 background-color
와 같은 다른 속성은 서로를 오버라이드 하지 않아요. background-color
에 영향을 주지 않고 모든 <div>
의 color
를 빨간색으로 설정할 수 있어요. 비슷하게, 다른 리액트 컨텍스트도 서로를 오버라이드 하지 않아요. createContext()
로 생성된 각 컨텍스트는 완벽하게 서로 분리되어 있고 해당 컨텍스트를 사용하고 제공하는 컴포넌트만 연결해요. 하나의 컴포넌트는 문제 없이 여러 컨텍스트를 사용하거나 제공할 수 있어요.
Before you use context | 컨텍스트를 사용하기 전에...
컨텍스트는 사용하고 싶은 유혹이 굉장히 강하게 들어요! 그러나 이는 곧 남용하기 쉽다는 것을 의미하기도 해요. 왜냐하면 몇 단계 아래로 어떤 props를 넘기는 것은 해당 정보를 context에 넣을 필요가 없다는 것을 의미하기 때문이에요.
컨텍스트를 사용하기 전에 고려애햐는 몇가지 방법이 있어요.
- props 전달로 시작하세요. 만약 컴포넌트가 너무 하찮지 않다면 12개의 컴포넌트를 통해 12개의 props를 전달하는 것은 이상한 상황이 아니에요. 슬로건처럼 느껴질 수도 있지만 어떤 컴포넌트가 어떤 데이터를 사용하는지를 더욱 명확하게 만들어줘요. 여러분이 작성한 코드를 유지하는 사람은 여러분이 데이터의 흐름을 props로 명시한다면 기뻐할 거예요.
- 컴포넌트를 추출하고 이들을
children
으로 JSX에 전달하세요. 만약 어떤 데이터가 그 데이터를 사용하지 않는 (그리고 데이터를 전달하기만 하는) 수겹의 중간 컴포넌트를 통과하여 전달되어야 한다면 이는 종종 여러분이 어떤 컴포넌트를 추출하는 것을 까먹었다는 것을 의미해요. 예를 들어, 여러분은posts
와 같은 데이터 props를<Layout posts={posts} />
와 같이 이 데이터를 사용하지 않는 시각적인 컴포넌트에 전달한다고 생각해요.Layout
이children
을 prop으로 받고<Layout><Posts posts={posts} /></Layout>
을 렌더링하도록 만드세요. 이렇게 하면 데이터를 지정한 컴포넌트와 필요한 컴포넌트 사이의 수많은 층들이 제거될 거예요.
이 두 방법 모두가 여러분의 상황과 맞지 않다면 컨텍스트를 고민해보세요.
Use cases for context | 컨텍스트 사용 사례
- 테마 지정하기: 만약 여러분의 앱에서 사용자가 (다크 모드와 같이) 외적인 부분을 변경할 수 있다면 컨텍스트 프로바이더를 앱의 최상위에 추가하고 시각적인 부분을 조정하는데 필요한 컨텍스트를 컴포넌트 안에서 사용하세요.
- 현재 계정: 많은 컴포넌트는 현재 로그인한 사용자를 알 필요가 있어요. 이 정보를 컨텍스트에 넣으면 트리의 어디에서도 읽기 편해요. 어떤 앱은 한 번에 여러 계정을 운영하는 것을 허용해요. (다른 사용자로 덧글 달기 등) 이런 경우에는 UI의 일부를 서로 다른 현재 계정 값을 가진 중첩 프로바이더로 감싸는 것이 편리해요.
- 라우팅: 대부분의 라우팅 솔루션은 내부적으로는 현재 라우트를 컨텍스트에 갖고 있어요. 이것이 바로 모든 링크가 여러분이 그곳에서 활동 중인지 아닌지를 "알 수 있는" 이유에요. 만약 여러분이 직접 라우터를 만들었다면 이렇게 하고 싶을 거예요.
- 상태 관리: 앱이 커지면 앱의 최상단과 근접한 상태가 많을 거에요. 거리가 먼 수많은 하위 컴포넌트는 상태를 바꾸고 싶을 수도 있어요. 컨텍스트와 리듀서를 같이 사용하는 것은 복잡한 상태를 관리하고 원거리에 있는 컴포넌트에 큰 혼란 없이 이를 전달하는 일반적인 방법이에요.
컨텍스트는 정적 값으로 제한되지 않아요. 만약 다음 렌더링에서 다른 값을 전달하고 싶다면 리액트는 아래에서 그것을 읽는 모든 컴포넌트를 업데이트해요. 컨텍스트가 종종 상태와 함께 사용되는 이유가 바로 이 때문이에요.
일반적으로 만약 어떤 정보가 다른 파트의 트리에 있는 원거리의 컴포넌트에서 필요하다면 컨텍스트가 여러분을 도와줄 수 있다는 좋은 표시에요.
Recap | 요약
- 컨텍스트는 컴포넌트가 어떤 정보를 전체 하위 트리에 제공하도록 만들어줘요.
- 컨텍스트를 전달하려면 :
export const MyContext = createContext(defaultValue)
를 사용하여 컨텍스트를 생성하고 추출하세요.- 깊이 상관 없이 자식 컴포넌트에서 읽을 수 있도록
useContext(MyContext)
훅에 컨텍스트를 전달하세요. - 부모로 부터 컨텍스트를 제공받기 위해
<MyContext.Provider value={...}>
로 자식을 감싸세요.
- 컨텍스트는 중간의 여러 컴포넌트를 통해 전달해요.
- 컨텍스트는 "주변에 잘 적응하는" 컴포넌트를 작성하도록 도와줘요.
- 컨텍스트를 사용하지 전에 props로 전달하거나
children
으로 JSX를 전달해보세요.
Challenges | 도전 과제
컨텍스트로 prop drilling 대체하기
이 예시에서 체크박스를 선택하거나 선택을 해제하면 imageSize
prop이 각 <PlaceImage>
로 전달돼요. 체크박스의 상태는 최상단의 App
컴포넌트에서 가지고 있지만 <PlaceImage>
는 이를 알아야해요.
현재 App
은 imageSize
를 List
로 전달하고, List
는 각 Place
로 전달하며, Place
는 PlaceImage
로 전달해요. imageSize
prop을 제거하고 App
컴포넌트에서 직접 PlaceImage
로 전달하세요.
Context.js
에서 컨텍스트를 선언하세요.
Solution
imageSize
prop을 모든 컴포넌트에서 제거하세요. Context.js
에서 ImageSize Context
를 만들고 추출하세요. 그리고나서 값을 내리기 위해 List를 <ImageSizeContext.Provider value={imageSize}>
로 감싸고 PlaceImage
에서 읽기 위해 useContext(ImageSizeContext)
를 사용하세요