https://react.dev/learn/passing-data-deeply-with-context를 읽고 정리한다.
보통 부모 컴포넌트에서 자식 컴포넌트로 props를 통해 정보를 전달한다. 하지만 그 중간에 많은 컴포넌트를 거쳐야 하는 경우, 코드가 장황해지고 불편해진다.
Context
를 사용하면 부모 컴포넌트가 props를 통해 명시적으로 전달하지 않고도 하위 트리의 모든 컴포넌트에서 일부 정보를 사용할 수 있도록 한다.
Props 전달할때의 문제 : Prop driling
tree를 통해 일부 prop
을 깊게 전달해야하거나 많은 컴포넌트에 동일한 prop
이 필요한 경우, 상태를 높이 올려서(lifting state up) 해결한다. 이 방법을 "prop driling"이라고 한다. 이럴 경우, 반복적으로 props를 전달해야 한다는 단점이 있다.
Context : 반복적인 prop
전달을 대체하는 방법
context
를 사용하면 상위 컴포넌트가 그 아래의 전체 트리에 데이터를 제공할 수 있다.
여기서 사용되는 예는 다음과 같다. 이를 context
를 사용해 개선해보자.
- Section 내부에 여러 개의 Heading이 존재한다.
- 동일한 Section 내에는 동일한 Level의 Heading만 존재할 수 있다.
- 기존 코드는 Heading에서 prop으로 Level을 입력받고 있다.
1. context를 만든다.
context용 파일을 생성하고, 컴포넌트에서 사용할 수 있도록 export 한다.
createContext
의 인수로 디폴트 값(개체 포함 모든 타입 가능)을 설정할 수 있다.
// LevelContext.js
import { createContext } from 'react';
export const LevelContext = createContext(1);
2. data가 필요한 컴포넌트에서 context를 사용한다.
2-1. useContext
Hook과 아까 생성한 context를 import 한다.
import { useContext } from 'react';
import { LevelContext } from './LevelContext.js';
2-2. 기존 컴포넌트(여기서는 Heading)의 props
중 context
로 대체될 것은 제거하고, 제거된 prop를 가져오는 곳에는 context를 사용한다.
useContext
Hook은Heading
컴포넌트가LevelContext
를 읽기를 원하다는 것을 React에 알리는 역할을 한다.context
를 제공하지않으면 React는 지정된 기본값을 사용한다.
// 기존 컴포넌트: level이 컨텍스트로 대체될 예정
export default function Heading({ level, children }) {
// ...
}
// 컴포넌트가 context를 사용하도록 변경
export default function Heading({ children }) {
const level = useContext(LevelContext);
// ...
}
2-3. 기존 컴포넌트를 사용하는 곳에서 context
로 대체되어 제거된 prop
에 해당하는 부분을 제거한다.
2-4. 그 대신 제거된 prop
은 context
를 제공할 컴포넌트에 전달한다.(여기서는 Section
)
3. 데이터를 설정하는 컴포넌트에서 context
를 제공한다.
context provider로 래핑 하여 LevelContext
에게 값을 제공한다.
- 이는
Section
내의 컴포넌트가LevelContext
를 요청하면 여기에 주어진level
을 제공하라고 React에게 말하는 것과 같다. - 단, 컴포넌트는 자신보다 위인 것 중 가장 가까운
<LevelContext.Provider>
를 사용한다.
// 기존 Section
export default function Section({ children }) {
return (
<section className="section">
{children}
</section>
);
}
// LevelContext 제공자를 래핑하도록 변경
import { LevelContext } from './LevelContext.js';
export default function Section({ level, children }) {
return (
<section className="section">
<LevelContext.Provider value={level}>
{children}
</LevelContext.Provider>
</section>
);
}
결과
위의 3단계를 거치면 아래와 같이 코드가 완성된다.
각각의 Heading
컴포넌트 에 level
prop을 사용하지 않는다. 대신 가장 가까운 Section
에 아래와 같이 요청해서 파악한다.
<Section>
에서 level prop을 전달한다.Section
에서 자식 요소를<LevelContext.Provider value={level}>
로 묶는다.Heading
은useContext(LevelContext)
를 사용해 가장 가까운LevelContext
의 값을 요청한다.
동일한 컴포넌트에서 context 사용 및 제공
context를 사용하면 위의 컴포넌트를 통해 정보를 읽을 수 있기 때문에, 각각의 Section
은 그 위의 Section으로부터
level
을 읽고, 자동적으로 level + 1
을 전달할 수 있다.
Heading
,Section
에서는LevelContext
을 읽고 얼마나 깊이 있는지(level)를 파악한다.Section
은LevelContext
내children
를 래핑 하여children
가 "더 깊은" level(level + 1)에 있도록 지정한다.
// 기존 코드
export default function Page() {
return (
<Section level={1}>
...
<Section level={2}>
...
<Section level={3}>
...
// context를 사용
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>
);
}
이 예제는 중첩된 컴포넌트가 context를 재정의할 수 있는 방법을 시각적으로 보여주기 위해 heading
level을 사용한다.
context를 사용하면 "주변에 적응"해서 렌더링 되는 위치 (또는 컨텍스트)에 따라 다르게 표시되는 컴포넌트를 작성할 수 있다.
context를 제공하는 컴포넌트와 context를 사용하는 컴포넌트 사이에 얼마든지 컴포넌트를 삽입할 수 있다.
- 삽입 가능 컴포넌트 : built-in 컴포넌트(`div`...) 또는 직접 만든 컴포넌트
따라서 동일한 컴포넌트라도 다른 중첩 레벨에 넣으면 자동적으로 가까운 context의 정보를 이용해 렌더링 된다.
아래 예제에서는 Post
컴포넌트가 어디에 위치하느냐에 따라 자동으로 <Heading>
level이 적용된다.(App.js 참고)
다른 React context는 서로를 재정의하지 않는다. createContext()
를 사용해 만들어지는 각 context는 다른 context와 완전히 분리되어 있으며 특정 context를 사용 및 제공하는 컴포넌트를 함께 묶는다. 따라서 하나의 컴포넌트는 문제없이 많은 다른 context를 사용하거나 제공할 수 있다.
context를 사용하기 전에
context를 사용하기 쉬운만큼 남용하기 쉽다. 일부 props를 여러 level의 깊이로 전달해야 한다고 해서 항상 이 정보를 컨텍스트에 넣어야 하는 것은 아니다.
context를 사용하기 전에 고려할 수 있는 대안이 몇 가지 있다.
- props를 전달하기.
- 중요한 컴포넌트라면 수십 개의 컴포넌트를 통해 수십개의 prop을 전달하는 방법은 자주 사용된다.
- 유지 보수 측면에서 props를 사용해 데이터 흐름(어떤 컴포넌트가 어떤 데이터를 사용하는지)을 명시적으로 만드는 것이 좋을 수 있다.
- 컴포넌트를 추출하고 JSX를 children으로 전달하기.
- 데이터를 전달할 때 해당 데이터를 사용하지 않는 중간 컴포넌트들이 많을 경우(또 더 아래로만 전달할 경우), 이는 종종 도중에 일부 컴포넌트 추출을 제대로 하지 않았다는 의미일 수 있다.
- 예
- 잘못된 예 :
posts
와 같은 data props을 직접 사용하지 않는 컴포넌트에 즉시 전달.- `<Layout posts={posts} />`
- 올바른 예 :
Layout
에children
을 porp으로 두고, 아래처럼 렌더링. => 이렇게 하면 데이터를 지정하는 컴포넌트와 이를 필요로 하는 컴포넌트 사이의 계층 수가 줄어든다.- `<Layout><Posts posts={posts} /></Layout>`
- 잘못된 예 :
context를 위한 유즈 케이스
- Theming: 앱에서 사용자가 모양을 변경할 수 있는 경우 (e.g. dark mode), context provider를 앱 상단에 배치하고, 시각적인 요소를 조정해야 하는 컴포넌트에서 해당 컨텍스트를 사용할 수 있다.
- Current account: 많은 컴포넌트가 현재 로그인한 사용자를 알아야 할 수 있다. context 사용하면 트리의 어느 곳에서 나 쉽게 데이터를 읽을 수 있다. 일부 앱에서는 동시에 여러 계정을 운영할 수 도 있다. (예 : 다른 사용자로 댓글 남기기). 이때, UI 일부를 nested provider로 (현재 계정 값과) 다른 값을 래핑 하는 것이 편리하다.
- Routing: 대부분의 라우팅 솔루션은 현재 경로를 유지하기 위해 내부적으로 context 를 사용한다. 이것이 모든 링크의 활성 여부를 "알 수 있는" 방법이다. 자신의 라우터를 구축하는 경우에도 그렇게 하고 싶을 수 있다.
- Managing state: 앱이 성장함에 따라 앱 상단에 더 많은 상태가 생길 수 있다. 아래에 거리가 먼 많은 컴포넌트가 상태를 변경하려고 할 수 있다. 복잡한 상태를 관리하고 너무 많은 번거로움 없이 멀리 떨어진 구성 요소로 전달하기 위해 context와 함께 reducer를 사용하는 것이 일반적이다.
context의 값은 변경될 수 있다. 다음 렌더에서 다른 값을 전달하면, React는 아래에서 읽는 모든 구성 요소를 업데이트한다! 이것이 context가 종종 state와 함께 사용되는 이유이다.
일반적으로 트리의 다른 부분에 있는 멀리 떨어진 구성 요소에 일부 정보가 필요한 경우 컨텍스트가 도움이 될 것이라는 좋은 표시이다.
'React > Learn React' 카테고리의 다른 글
Escape Hatches ☞ Referencing Values with Refs (0) | 2022.12.04 |
---|---|
Managing State ☞ Scaling Up with Reducer and Context (0) | 2022.10.31 |
Managing State ☞ Extracting State Logic into a Reducer (1) | 2022.10.03 |
Managing State ☞ Sharing State Between Components (0) | 2022.10.01 |
Managing State ☞ Choosing the State Structure (0) | 2022.10.01 |