v19.2Latest

Context로 데이터 깊숙이 전달하기

일반적으로 부모 컴포넌트에서 자식 컴포넌트로 props를 통해 정보를 전달합니다. 하지만 props를 전달하는 것은 중간에 많은 컴포넌트를 거쳐야 하거나, 앱의 여러 컴포넌트가 동일한 정보를 필요로 하는 경우에는 장황하고 불편해질 수 있습니다.Context를 사용하면 부모 컴포넌트가 props를 명시적으로 전달하지 않고도 그 아래 트리의 모든 컴포넌트(아무리 깊숙이 있어도)에 일부 정보를 사용할 수 있게 만들 수 있습니다.

배울 내용
  • “prop drilling”이 무엇인지
  • 반복적인 prop 전달을 context로 대체하는 방법
  • context의 일반적인 사용 사례
  • context의 일반적인 대안

props 전달의 문제점

Props 전달은 UI 트리를 통해 데이터를 명시적으로 사용하는 컴포넌트로 파이프하는 훌륭한 방법입니다.

하지만 어떤 prop을 트리 깊숙이 전달해야 하거나, 많은 컴포넌트가 동일한 prop을 필요로 하는 경우에는 props 전달이 장황하고 불편해질 수 있습니다. 가장 가까운 공통 조상이 데이터가 필요한 컴포넌트에서 멀리 떨어져 있을 수 있으며, 상태를 그렇게 높게끌어올리면“prop drilling”이라고 불리는 상황이 발생할 수 있습니다.

상태 끌어올리기

세 개의 컴포넌트로 이루어진 트리 다이어그램. 부모는 보라색으로 강조된 값을 나타내는 버블을 포함합니다. 값은 두 자식 모두에게 아래로 흐르며, 둘 다 보라색으로 강조되어 있습니다.세 개의 컴포넌트로 이루어진 트리 다이어그램. 부모는 보라색으로 강조된 값을 나타내는 버블을 포함합니다. 값은 두 자식 모두에게 아래로 흐르며, 둘 다 보라색으로 강조되어 있습니다.

Prop drilling

각 노드가 두 개 이하의 자식을 가진 열 개의 노드로 이루어진 트리 다이어그램. 루트 노드는 보라색으로 강조된 값을 나타내는 버블을 포함합니다. 값은 두 자식을 통해 아래로 흐르며, 각 자식은 값을 전달하지만 값을 포함하지는 않습니다. 왼쪽 자식은 값을 아래의 두 자식에게 전달하며, 두 자식 모두 보라색으로 강조되어 있습니다. 루트의 오른쪽 자식은 값을 두 자식 중 하나(오른쪽 자식)에게 전달하며, 그 자식은 보라색으로 강조되어 있습니다. 그 자식은 값을 자신의 단일 자식에게 전달하며, 그 자식은 값을 자신의 두 자식에게 전달하고, 두 자식 모두 보라색으로 강조되어 있습니다.각 노드가 두 개 이하의 자식을 가진 열 개의 노드로 이루어진 트리 다이어그램. 루트 노드는 보라색으로 강조된 값을 나타내는 버블을 포함합니다. 값은 두 자식을 통해 아래로 흐르며, 각 자식은 값을 전달하지만 값을 포함하지는 않습니다. 왼쪽 자식은 값을 아래의 두 자식에게 전달하며, 두 자식 모두 보라색으로 강조되어 있습니다. 루트의 오른쪽 자식은 값을 두 자식 중 하나(오른쪽 자식)에게 전달하며, 그 자식은 보라색으로 강조되어 있습니다. 그 자식은 값을 자신의 단일 자식에게 전달하며, 그 자식은 값을 자신의 두 자식에게 전달하고, 두 자식 모두 보라색으로 강조되어 있습니다.

props를 전달하지 않고도 트리에서 데이터가 필요한 컴포넌트로 데이터를 “순간이동”시키는 방법이 있다면 좋지 않을까요? React의 context 기능을 사용하면 가능합니다!

Context: props 전달의 대안

Context를 사용하면 부모 컴포넌트가 그 아래 전체 트리에 데이터를 제공할 수 있습니다. context에는 많은 용도가 있습니다. 여기 한 가지 예가 있습니다. 크기에 대한Heading컴포넌트를 고려해 보세요. 이 컴포넌트는level을 받아 크기를 결정합니다:

동일한 Section내에 있는 여러 제목이 항상 동일한 크기를 갖도록 하고 싶다고 가정해 보세요:

현재는 각 levelprop을 각<Heading>에 개별적으로 전달합니다:

대신 levelprop을<Section> 컴포넌트에 전달하고 <Heading>에서 제거할 수 있다면 좋을 것입니다. 이렇게 하면 동일한 섹션 내의 모든 제목이 동일한 크기를 갖도록 강제할 수 있습니다:

그러나 <Heading> 컴포넌트가 가장 가까운 <Section>의 레벨을 어떻게 알 수 있을까요?이는 자식이 트리 상위 어딘가에서 데이터를 "요청"할 수 있는 방법이 필요합니다.

props만으로는 이 작업을 수행할 수 없습니다. 여기서 컨텍스트가 작동합니다. 세 단계로 수행할 것입니다:

  1. 컨텍스트 생성(제목 레벨을 위한 것이므로LevelContext라고 부를 수 있습니다.)
  2. 사용데이터가 필요한 컴포넌트에서 해당 컨텍스트를 사용합니다. (HeadingLevelContext)
  3. 제공데이터를 지정하는 컴포넌트에서 해당 컨텍스트를 제공합니다. (SectionLevelContext)

컨텍스트를 사용하면 부모 컴포넌트(심지어 멀리 떨어진 부모라도!)가 그 안에 있는 전체 트리에 일부 데이터를 제공할 수 있습니다.

가까운 자식에서 컨텍스트 사용하기

세 개의 컴포넌트로 이루어진 트리 다이어그램. 부모는 주황색으로 강조된 값을 나타내는 버블을 포함하며, 이 버블은 주황색으로 강조된 두 자식에게 아래로 투영됩니다.세 개의 컴포넌트로 이루어진 트리 다이어그램. 부모는 주황색으로 강조된 값을 나타내는 버블을 포함하며, 이 버블은 주황색으로 강조된 두 자식에게 아래로 투영됩니다.

먼 자식에서 컨텍스트 사용하기

각 노드가 두 개 이하의 자식을 가진 열 개의 노드로 이루어진 트리 다이어그램. 루트 부모 노드는 주황색으로 강조된 값을 나타내는 버블을 포함합니다. 이 값은 트리 내의 네 개의 리프 노드와 하나의 중간 컴포넌트로 직접 투영되어 모두 주황색으로 강조됩니다. 다른 중간 컴포넌트들은 강조되지 않습니다.각 노드가 두 개 이하의 자식을 가진 열 개의 노드로 이루어진 트리 다이어그램. 루트 부모 노드는 주황색으로 강조된 값을 나타내는 버블을 포함합니다. 이 값은 트리 내의 네 개의 리프 노드와 하나의 중간 컴포넌트로 직접 투영되어 모두 주황색으로 강조됩니다. 다른 중간 컴포넌트들은 강조되지 않습니다.

1단계: 컨텍스트 생성하기

먼저 컨텍스트를 생성해야 합니다. 컴포넌트에서 사용할 수 있도록파일에서 내보내야 합니다:

의 유일한 인자는createContext 기본값입니다. 여기서1은 가장 큰 제목 수준을 의미하지만, 어떤 종류의 값(객체도 가능)을 전달할 수 있습니다. 기본값의 중요성은 다음 단계에서 확인할 수 있습니다.

2단계: 컨텍스트 사용하기

React에서useContextHook과 컨텍스트를 가져옵니다:

현재 Heading컴포넌트는 props에서level을 읽습니다:

대신 levelprop을 제거하고 방금 가져온 컨텍스트인LevelContext에서 값을 읽으세요:

useContext는 Hook입니다.useStateuseReducer와 마찬가지로, Hook은 React 컴포넌트 내부(반복문이나 조건문 내부가 아닌)에서 즉시 호출해야 합니다.useContext는 React에게Heading 컴포넌트가 LevelContext를 읽고자 한다고 알려줍니다.

이제 Heading 컴포넌트에 levelprop이 없으므로, 다음과 같이 JSX에서Heading에 level prop을 전달할 필요가 없습니다:

JSX를 업데이트하여 대신Section이 이를 받도록 하세요:

다시 말씀드리자면, 이것은 동작하도록 만들려고 했던 마크업입니다:

이 예제가 아직 제대로 작동하지 않는다는 점을 주목하세요! 모든 제목이 같은 크기를 갖는 이유는컨텍스트를 사용하고 있지만 아직 컨텍스트를 제공하지 않았기때문입니다.React는 어디서 컨텍스트를 가져와야 할지 모릅니다!

컨텍스트를 제공하지 않으면 React는 이전 단계에서 지정한 기본값을 사용합니다. 이 예제에서는1createContext의 인수로 지정했으므로useContext(LevelContext)1을 반환하여 모든 제목을<h1>로 설정합니다. 각 Section이 자체 컨텍스트를 제공하도록 하여 이 문제를 해결해 보겠습니다.

3단계: 컨텍스트 제공하기

현재Section컴포넌트는 자식 요소들을 렌더링합니다:

컨텍스트 제공자로 감싸서 그들에게 LevelContext를 제공하세요:

이는 React에게 다음과 같이 알립니다: "이<Section>안에 있는 어떤 컴포넌트가LevelContext를 요청하면, 이level값을 제공하라." 컴포넌트는 UI 트리에서 자신의 위에 있는 가장 가까운<LevelContext>의 값을 사용하게 됩니다.

원본 코드와 결과는 동일하지만, 각 Heading컴포넌트에levelprop을 전달할 필요가 없었습니다! 대신, 위에 있는 가장 가까운Section에 물어보는 방식으로 자신의 제목 레벨을 "파악"합니다:

  1. 당신은 levelprop을<Section>에 전달합니다.
  2. Section은 자식 요소를<LevelContext value={level}>로 감쌉니다.
  3. Heading은 위쪽에서 가장 가까운LevelContext 값을 useContext(LevelContext)로 읽어옵니다.

동일한 컴포넌트에서 컨텍스트 사용 및 제공하기

현재는 각 섹션의 level을 수동으로 지정해야 합니다:

컨텍스트를 사용하면 위쪽 컴포넌트의 정보를 읽을 수 있으므로, 각Section은 위쪽 Sectionlevel을 읽고 level + 1을 자동으로 아래로 전달할 수 있습니다. 이를 수행하는 방법은 다음과 같습니다:

이 변경으로 인해 levelprop을또한 <Section>이나 <Heading>에 전달할 필요가 없습니다:

이제 HeadingSection 모두 LevelContext를 읽어 자신이 얼마나 "깊은" 위치에 있는지 파악합니다. 그리고Section은 자식 요소를 LevelContext로 감싸 그 안에 있는 모든 것이 "더 깊은" 레벨에 있음을 지정합니다.

참고

이 예제는 중첩된 컴포넌트가 컨텍스트를 어떻게 재정의하는지 시각적으로 보여주기 위해 제목 레벨을 사용합니다. 하지만 컨텍스트는 다른 많은 사용 사례에서도 유용합니다. 전체 하위 트리에 필요한 모든 정보(현재 색상 테마, 현재 로그인한 사용자 등)를 아래로 전달할 수 있습니다.

컨텍스트는 중간 컴포넌트를 통과합니다

컨텍스트를 제공하는 컴포넌트와 사용하는 컴포넌트 사이에 원하는 만큼 많은 컴포넌트를 삽입할 수 있습니다. 여기에는<div>와 같은 내장 컴포넌트와 직접 빌드한 컴포넌트가 모두 포함됩니다.

이 예제에서 동일한Post컴포넌트(점선 테두리)가 두 개의 다른 중첩 레벨에서 렌더링됩니다. 그 안에 있는<Heading>이 가장 가까운 <Section>에서 자동으로 레벨을 가져오는 것을 확인하세요:

이것이 작동하도록 특별히 어떤 작업도 수행하지 않았습니다.Section은 그 안에 있는 트리의 컨텍스트를 지정하므로,<Heading>을 어디에든 삽입할 수 있으며 올바른 크기를 갖게 됩니다. 위의 샌드박스에서 시도해 보세요!

컨텍스트를 사용하면 컴포넌트가 "주변 환경에 적응"하고 어디에서(또는 다른 말로,어떤 컨텍스트 안에서) 렌더링되는지에 따라 다르게 표시되도록 작성할 수 있습니다.

컨텍스트의 작동 방식은CSS 속성 상속을 떠올리게 할 수 있습니다. CSS에서는color: blue<div>에 지정할 수 있으며, 그 안에 있는 모든 DOM 노드는 중간에 다른 DOM 노드가color: green으로 재정의하지 않는 한 그 색상을 상속받습니다. 마찬가지로 React에서 위에서 오는 컨텍스트를 재정의하는 유일한 방법은 자식을 다른 값으로 컨텍스트 제공자로 감싸는 것입니다.

CSS에서 colorbackground-color와 같은 서로 다른 속성은 서로 재정의하지 않습니다. 모든<div>color를 빨간색으로 설정해도background-color에는 영향을 미치지 않습니다. 마찬가지로,서로 다른 React 컨텍스트도 서로 재정의하지 않습니다. createContext()로 만드는 각 컨텍스트는 다른 컨텍스트와 완전히 분리되어 있으며,그 특정컨텍스트를 사용하고 제공하는 컴포넌트들을 연결합니다. 하나의 컴포넌트가 문제 없이 여러 다른 컨텍스트를 사용하거나 제공할 수 있습니다.

컨텍스트 사용 전에

컨텍스트는 사용하기 매우 매력적입니다! 그러나 이는 또한 너무 쉽게 남용될 수 있다는 의미이기도 합니다.일부 props를 여러 단계 깊이 전달해야 한다고 해서 그 정보를 컨텍스트에 넣어야 한다는 의미는 아닙니다.

컨텍스트를 사용하기 전에 고려해야 할 몇 가지 대안은 다음과 같습니다:

  1. 먼저 props 전달부터 시작하세요. 컴포넌트가 사소하지 않다면, 수십 개의 컴포넌트를 통해 수십 개의 props를 전달하는 것은 드문 일이 아닙니다. 지루하게 느껴질 수 있지만, 어떤 컴포넌트가 어떤 데이터를 사용하는지 매우 명확해집니다! 코드를 유지 관리하는 사람은 props를 통해 데이터 흐름을 명시적으로 만든 것을 기뻐할 것입니다.
  2. 컴포넌트를 추출하고자식으로 JSX를 전달하세요.데이터를 사용하지 않는(그리고 단지 더 아래로 전달만 하는) 여러 중간 컴포넌트 계층을 통해 일부 데이터를 전달한다면, 이는 종종 경로를 따라 일부 컴포넌트를 추출하는 것을 잊었다는 의미입니다. 예를 들어,posts와 같은 데이터 props를 직접 사용하지 않는 시각적 컴포넌트(예:<Layout posts={posts} />)에 전달할 수 있습니다. 대신,Layoutchildren을 prop으로 받아들이고<Layout><Posts posts={posts} /></Layout>를 렌더링하도록 만드세요. 이렇게 하면 데이터를 지정하는 컴포넌트와 데이터가 필요한 컴포넌트 사이의 계층 수가 줄어듭니다.

이러한 접근 방식 중 어느 것도 잘 작동하지 않는다면 컨텍스트를 고려하세요.

컨텍스트의 사용 사례

  • 테마:앱에서 사용자가 외관을 변경할 수 있도록 허용하는 경우(예: 다크 모드), 앱 상단에 컨텍스트 제공자를 배치하고 시각적 모양을 조정해야 하는 컴포넌트에서 해당 컨텍스트를 사용할 수 있습니다.
  • 현재 계정:많은 컴포넌트가 현재 로그인한 사용자를 알아야 할 수 있습니다. 컨텍스트에 넣으면 트리 어디에서나 읽기 편리합니다. 일부 앱은 동시에 여러 계정을 운영할 수도 있습니다(예: 다른 사용자로 댓글을 남기기). 이러한 경우, UI의 일부를 다른 현재 계정 값으로 중첩된 제공자로 감싸는 것이 편리할 수 있습니다.
  • 라우팅:대부분의 라우팅 솔루션은 내부적으로 현재 경로를 보유하기 위해 컨텍스트를 사용합니다. 이것이 모든 링크가 활성 상태인지 아닌지를 "알게" 되는 방식입니다. 자신만의 라우터를 구축한다면, 당신도 그렇게 하고 싶을 수 있습니다.
  • 상태 관리:앱이 커질수록, 앱 상단에 가까운 많은 상태를 가지게 될 수 있습니다. 아래쪽의 많은 멀리 떨어진 컴포넌트들이 이를 변경하고 싶어할 수 있습니다. 복잡한 상태를 관리하고 번거로움 없이 멀리 떨어진 컴포넌트에 전달하기 위해리듀서와 컨텍스트를 함께 사용하는 것이 일반적입니다.

컨텍스트는 정적 값에만 국한되지 않습니다. 다음 렌더링에서 다른 값을 전달하면, React는 아래쪽에서 이를 읽는 모든 컴포넌트를 업데이트할 것입니다! 이것이 컨텍스트가 종종 상태와 결합하여 사용되는 이유입니다.

일반적으로, 트리의 다른 부분에 있는 멀리 떨어진 컴포넌트들이 어떤 정보를 필요로 한다면, 그것은 컨텍스트가 도움이 될 것이라는 좋은 신호입니다.

요약

  • 컨텍스트는 컴포넌트가 아래쪽 전체 트리에 일부 정보를 제공할 수 있게 합니다.
  • 컨텍스트를 전달하려면:
    1. 다음을 사용하여 생성하고 내보냅니다:export const MyContext = createContext(defaultValue).
    2. 자식 컴포넌트에서 읽으려면useContext(MyContext)훅에 전달하세요. 깊이에 상관없이 가능합니다.
    3. 부모에서 제공하려면 자식을<MyContext value={...}>로 감싸세요.
  • 컨텍스트는 중간에 있는 모든 컴포넌트를 통과합니다.
  • 컨텍스트를 사용하면 "주변 환경에 적응하는" 컴포넌트를 작성할 수 있습니다.
  • 컨텍스트를 사용하기 전에, props를 전달하거나 JSX를children으로 전달해 보세요.

도전 과제를 시도해 보세요

Challenge 1 of 1:프롭 드릴링을 컨텍스트로 대체하기 #

이 예시에서 체크박스를 토글하면 각 <PlaceImage>에 전달되는 imageSize prop이 변경됩니다. 체크박스 상태는 최상위 App 컴포넌트에서 관리되지만, 각 <PlaceImage>는 이 상태를 인식해야 합니다.

현재 AppimageSizeList에 전달하고, List는 각 Place에 전달하며, PlacePlaceImage에 전달합니다. imageSize prop을 제거하고, 대신 App 컴포넌트에서 직접 PlaceImage에 전달하도록 변경하세요.

컨텍스트는 Context.js에서 선언할 수 있습니다.