88

최근 30분간 동시 방문자 수를 표시합니다. (~)

최고 동시 방문자 수 -
어제: 0명 / 오늘: 0명

React Context API

React Context API

info

이 글에서 제공하는 예제는 React 프로젝트 시작하기 w. Vite 에서 생성한 프로젝트를 기반으로 합니다.

React Context API는 프로젝트의 상위/하위 컴포넌트 간 데이터 공유 방식입니다.
다음의 프로젝트 구조에서 시작해 Context API의 개념과 사용법을 이해해 봅시다.

content_copy
L
L
L
L
L
L
L
L
L
1
2
3
4
5
6
7
8
9
├─src/ │ ├─components/ │ │ ├─A1.tsx │ │ ├─A2.tsx │ │ ├─B1.tsx │ │ ├─B2.tsx │ │ ├─B3_1.tsx │ │ └─B3_2.tsx │ └─App.tsx
프로젝트 구조

컴포넌트 구조 zoom_out_map
컴포넌트 구조

# Props

부모 컴포넌트가 자식 컴포넌트에게 데이터를 전달하는 방식으로 주로 Props를 사용합니다.
아주 간단히 데이터를 전달할 수 있지만, 자식 컴포넌트 이상으로 데이터를 전달하려면 중간 단계의 컴포넌트에서 불필요하게 데이터를 처리해야 하는 문제가 발생합니다.

이를 이해하기 위해 아래 예제를 살펴봅시다.
App 컴포넌트에서 관리하는 반응형 색상 데이터(red)를 A2B3_2 컴포넌트에서 사용하려고 합니다.
그러면 AppA2 컴포넌트 사이의 A1 컴포넌트가 데이터를 전달해야 하고, 마찬가지로 AppB3_2 컴포넌트 사이의 B1, B2 컴포넌트가 데이터를 전달해야 합니다.

Props로 데이터 전달 zoom_out_map
Props로 데이터 전달

이런 상황을 속성(데이터)이 여러 컴포넌트를 관통하는 것 같다고 해서 'Prop Drilling'이라고 부릅니다.

Prop Drilling zoom_out_map
Prop Drilling

/src/App.tsx
TSX
content_copy
L
L
L
L
L
L
L
L
L
L
L
L
L
L
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { useState } from 'react' import A1 from './components/A1' import B1 from './components/B1' export default function App() { const [color] = useState('red') return ( <> <div>App</div> <A1 color={color} /> <B1 color={color} /> </> ) }
Props로 데이터 전달
/src/components/A1.tsx
TSX
content_copy
L
L
L
L
L
L
L
L
L
L
1
2
3
4
5
6
7
8
9
10
import A2 from './A2' export default function A1({ color }: { color: string }) { return ( <> <div>A1</div> <A2 color={color} /> </> ) }
불필요한 데이터 처리
/src/components/A2.tsx
TSX
content_copy
L
L
L
1
2
3
export default function A2({ color }: { color: string }) { return <div style={{ color }}>A2</div> }
/src/components/B1.tsx
TSX
content_copy
L
L
L
L
L
L
L
L
L
L
1
2
3
4
5
6
7
8
9
10
import B2 from './B2' export default function B1({ color }: { color: string }) { return ( <> <div>B1</div> <B2 color={color} /> </> ) }
불필요한 데이터 처리
/src/components/B2.tsx
TSX
content_copy
L
L
L
L
L
L
L
L
L
L
L
L
1
2
3
4
5
6
7
8
9
10
11
12
import B3_1 from './B3_1' import B3_2 from './B3_2' export default function B2({ color }: { color: string }) { return ( <> <div>B2</div> <B3_1 /> <B3_2 color={color} /> </> ) }
불필요한 데이터 처리
/src/components/B3_1.tsx
TSX
content_copy
L
L
L
1
2
3
export default function B3_1() { return <div>B3_1</div> }
/src/components/B3_2.tsx
TSX
content_copy
L
L
L
1
2
3
export default function B3_2({ color }: { color: string }) { return <div style={{ color }}>B3_2</div> }

# Context API

위에서 살펴본 Props 방식의 한계를 극복하기 위해 React에서는 Context API를 제공합니다.
Context API는 특정 컴포넌트에서 제공하는 데이터를 자식을 포함한 하위 컴포넌트에서 사용할 수 있게 합니다.

TSX
content_copy
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import { createContext, useContext } from 'react' // 초깃값과 함께 컨텍스트 생성 const MyContext = createContext(데이터) // 컨텍스트 생서자로 데이터 제공 <MyContext.Provider value={데이터}> {children} </MyContext.Provider> // 컨텍스트 사용으로 데이터 얻기 const 데이터 = useContext(MyContext) // 컨텍스트 소비자로 데이터 얻기 (선택) <MyContext.Consumer> {데이터 => <div>{데이터}</div>} </MyContext.Consumer> // 컨텍스트 이름 지정 (선택) MyContext.displayName = 'MyContext'
Context API

이번에도 App 컴포넌트의 색상 데이터를 A2B3_2 컴포넌트에서 사용하려고 합니다.
이를 위해 Context API를 활용하여 App 컴포넌트에서 색상 데이터를 제공하고, 그 하위 컴포넌트에서 사용하는 방식으로 구현할 수 있습니다.

Context API로 데이터 제공 및 사용 zoom_out_map
Context API로 데이터 제공 및 사용

App 컴포넌트에서 createContext 함수를 호출하여 초깃값과 함께 컨텍스트를 생성합니다.
생성된 컨텍스트 객체의 .Provider 컴포넌트로 데이터를 제공할 하위 컴포넌트를 감싸고, value 속성으로 전달할 데이터를 제공합니다.
이제 하위 컴포넌트에서 데이터를 사용할 수 있습니다.

/src/App.tsx
TSX
content_copy
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { createContext } from 'react' import A1 from './components/A1' import B1 from './components/B1' export const ColorContext = createContext('') export default function App() { const [color] = useState('red') // 색상 데이터 return ( <ColorContext.Provider value={color}> <div>App</div> <A1 /> <B1 /> </ColorContext.Provider> ) }

Props 방식과 달리 제공된 데이터를 사용하지 않는 컴포넌트에서는 별도로 처리할 것이 없습니다.

/src/components/A1.tsx
TSX
content_copy
L
L
L
L
L
L
L
L
L
L
1
2
3
4
5
6
7
8
9
10
import A2 from './A2' export default function A1() { return ( <> <div>A1</div> <A2 /> </> ) }
처리할 것이 없음

데이터를 사용할 컴포넌트에서는 useContext 훅(Hook)을 통해 제공된 데이터를 얻을 수 있습니다.
useContext 훅 사용 시 컨텍스트 객체를 인자로 전달하면 됩니다.

/src/components/A2.tsx
TSX
content_copy
L
L
L
L
L
L
L
1
2
3
4
5
6
7
import { useContext } from 'react' import { ColorContext } from '../App' export default function A2() { const color = useContext(ColorContext) // 'red' return <div style={{ color }}>A2</div> }
/src/components/B1.tsx
TSX
content_copy
L
L
L
L
L
L
L
L
L
L
1
2
3
4
5
6
7
8
9
10
import B2 from './B2' export default function B1() { return ( <> <div>B1</div> <B2 /> </> ) }
처리할 것이 없음
/src/components/B2.tsx
TSX
content_copy
L
L
L
L
L
L
L
L
L
L
L
L
1
2
3
4
5
6
7
8
9
10
11
12
import B3_1 from './B3_1' import B3_2 from './B3_2' export default function B2() { return ( <> <div>B2</div> <B3_1 /> <B3_2 /> </> ) }
처리할 것이 없음
/src/components/B3_1.tsx
TSX
content_copy
L
L
L
1
2
3
export default function B3_1() { return <div>B3_1</div> }
처리할 것이 없음
/src/components/B3_2.tsx
TSX
content_copy
L
L
L
L
L
L
L
1
2
3
4
5
6
7
import { useContext } from 'react' import { ColorContext } from '../App' export default function B3_2() { const color = useContext(ColorContext) // 'red' return <div style={{ color }}>B3_2</div> }

# 컨텍스트 모듈화

컨텍스트를 제공하는 컴포넌트와 사용하는 컴포넌트를 분리하고 재사용할 수 있도록 모듈화할 수 있습니다.

/src/providers 폴더에 color.tsx 파일을 생성해 모듈화를 진행합니다.
ColorProvider 컴포넌트를 만들어 슬롯으로 전달된 내용(children)을 컨텍스트의 .Provider 컴포넌트로 감싸고, 색상 데이터를 전달받아 제공합니다.
그리고 제공된 데이터를 각 컴포넌트에서 더 쉽게 사용할 수 있도록 useColor 함수도 만듭니다.

/src/providers/color.tsx
TSX
content_copy
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { createContext, useContext } from 'react' export const ColorContext = createContext('') // 컨텍스트 제공 export function ColorProvider({ children, color }: { children: React.ReactNode color: string }) { return ( <ColorContext.Provider value={color}> {children} </ColorContext.Provider> ) } // 컨텍스트 사용 export function useColor() { return useContext(ColorContext) }

이제 App 컴포넌트에서 모듈화된 ColorProvider 컴포넌트로 데이터를 제공합니다.

/src/App.tsx
TSX
content_copy
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { useState } from 'react' import { ColorProvider } from './providers/color' import A1 from './components/A1' import B1 from './components/B1' export default function App() { const [color] = useState('red') return ( <ColorProvider color={color}> <div>App</div> <A1 /> <B1 /> </ColorProvider> ) }

데이터를 사용하는 컴포넌트에서도 useContext 훅 대신 useColor 함수를 사용합니다.

/src/components/A2.tsx
TSX
content_copy
L
L
L
L
L
L
1
2
3
4
5
6
import { useColor } from '../providers/color' export default function A2() { const color = useColor() // 'red' return <div style={{ color }}>A2</div> }
/src/components/B3_2.tsx
TSX
content_copy
L
L
L
L
L
L
1
2
3
4
5
6
import { useColor } from '../providers/color' export default function B3_2() { const color = useColor() // 'red' return <div style={{ color }}>B3_2</div> }

# 하위 컴포넌트에서 데이터 수정

지금까지 살펴본 예제에서 반응형 색상 데이터는 App 컴포넌트가 소유하고 있고, 하위 컴포넌트는 데이터의 읽기만 가능했습니다.
만약 하위 컴포넌트에서 데이터를 수정할 수 있게 하려면, 수정 가능한 함수도 함께 제공해야 합니다.

우리는 useState 함수를 호출해 반환된 결과에서 배열 구조 분해 할당(Destructuring Assignment)으로 데이터와 수정 함수를 얻을 수 있습니다.
이번에는 이 수정 함수를 데이터와 함께 제공하여 하위 컴포넌트에서 사용할 수 있도록 해봅시다.

먼저, 컨텍스트를 생성합니다.
초깃값을 null로 설정하되, 이후 제공할 데이터 타입도 만들어 유니온 타입으로 함께 지정(<ColorState | null>)합니다.
데이터 타입(ColorState)은 useState 함수의 반환 타입과 동일하게 합니다.

주의할 점은 초깃값을 null로 설정했기 때문에, useContext 훅의 반환값이 null일 수 있다는 것입니다.
따라서 타입 가드나 타입 단언을 사용해야 합니다.

/src/providers/color.tsx
TSX
content_copy
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import { createContext, useContext } from 'react' type ColorState = [string, React.Dispatch<React.SetStateAction<string>>] export const ColorContext = createContext<ColorState | null>(null) // 컨텍스트 제공 export function ColorProvider({ children, color }: { children: React.ReactNode color: ColorState }) { return ( <ColorContext.Provider value={color}> {children} </ColorContext.Provider> ) } // 컨텍스트 사용 export function useColor() { return useContext(ColorContext)! // 타입 단언 }

App 컴포넌트에서는 색상 값뿐만 아니라 수정 함수를 포함하는 useState의 반환값을 그대로 제공합니다.

/src/App.tsx
TSX
content_copy
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { useState } from 'react' import { ColorProvider } from './providers/color' import A1 from './components/A1' import B1 from './components/B1' export default function App() { const colorState = useState('red') return ( <ColorProvider color={colorState}> <div>App</div> <A1 /> <B1 /> </ColorProvider> ) }

이제 하위 컴포넌트에서 useColor 함수를 사용하면 색상 값과 수정 함수를 함께 얻을 수 있습니다.
다음과 같이 버튼을 클릭하여 색상을 변경하는 것이 가능합니다.

/src/components/B3_2.tsx
TSX
content_copy
L
L
L
L
L
L
L
L
L
L
L
L
L
1
2
3
4
5
6
7
8
9
10
11
12
13
import { useColor } from '../providers/color' export default function B3_2() { const [color, setColor] = useColor() return ( <> <div style={{ color }}>B3_2</div> <button onClick={() => setColor('blue')}> Change! </button> </> ) }

# 동일 컨텍스트 중첩

필요한 경우 동일한 컨텍스트를 중첩해서 사용할 수 있습니다.
동일한 컨텍스트를 중첩하면 특정 하위 컴포넌트는 자신과 가장 가까운 상위 컴포넌트의 제공 데이터를 사용합니다.

동일 컨텍스트 중첩 zoom_out_map
동일 컨텍스트 중첩

앞선 예제로 확인했듯, App 컴포넌트에서는 ColorProvider를 통해 빨간색을 제공하고 있습니다.
그리고 다음과 같이 App의 하위 컴포넌트인 B1 컴포넌트에서 추가로 ColorProvider를 사용해 초록색을 제공합니다.

/src/B1.tsx
TSX
content_copy
L
L
L
L
L
L
L
L
L
L
L
L
L
1
2
3
4
5
6
7
8
9
10
11
12
13
import { useState } from 'react' import { ColorProvider } from '../providers/color' import B2 from './B2' export default function B1() { const colorState = useState('green') return ( <ColorProvider color={colorState}> <div>B1</div> <B2 /> </ColorProvider> ) }

이제 B3_2 컴포넌트에서 useColor 함수를 사용하면, 자신과 가장 가까운 상위 컴포넌트인 B1에서 제공하는 데이터(green)를 사용합니다.

/src/components/B3_2.tsx
TSX
content_copy
L
L
L
L
L
L
L
L
L
L
L
L
L
1
2
3
4
5
6
7
8
9
10
11
12
13
import { useColor } from '../providers/color' export default function B3_2() { const [color, setColor] = useColor() // 'green' return ( <> <div style={{ color }}>B3_2</div> <button onClick={() => setColor('blue')}> Change! </button> </> ) }

# 다중 컨텍스트 사용

하나의 프로젝트에서 여러 컨텍스트를 사용할 수 있습니다.

다음과 같이 새로운 count.tsx 모듈을 생성합니다.
이 모듈은 제공할 데이터를 외부에서 받지 않고 내부에서 관리합니다.

/src/providers/count.tsx
TSX
content_copy
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import { createContext, useContext, useState } from 'react' type Data = { count: number increase: () => void decrease: () => void } export const CountContext = createContext<Data>({} as Data) // 컨텍스트 제공 export function CountProvider({ children }: { children: React.ReactNode }) { const [count, setCount] = useState(0) const increase = () => setCount(c => c + 1) const decrease = () => setCount(c => c - 1) return ( <CountContext.Provider value={{ count, increase, decrease }}> {children} </CountContext.Provider> ) } // 컨텍스트 사용 export function useCount() { return useContext(CountContext) }

다음과 같이 App 컴포넌트에서 하위 컴포넌트를 감싸도록 컨텍스트를 제공할 수 있습니다.
중요한 점은 ColorProviderCountProvider가 서로 관련이 없는 컨텍스트이므로 위치는 전혀 상관없다는 것입니다.

/src/App.tsx
TSX
content_copy
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { useState } from 'react' import { ColorProvider } from './providers/color' import { CountProvider } from './providers/count' import A1 from './components/A1' import B1 from './components/B1' export default function App() { const colorState = useState('red') return ( <ColorProvider color={colorState}> <CountProvider> <div>App</div> <A1 /> <B1 /> </CountProvider> </ColorProvider> ) }

그리고 제공된 데이터에 맞게 하위 컴포넌트에서 적절히 사용합니다.

/src/components/B3_1.tsx
TSX
content_copy
L
L
L
L
L
L
L
L
L
L
L
L
L
1
2
3
4
5
6
7
8
9
10
11
12
13
import { useCount } from '../providers/count' export default function B3_1() { const { count, increase, decrease } = useCount() return ( <> <div>B3_1</div> <div>{count}</div> <button onClick={increase}>+</button> <button onClick={decrease}>-</button> </> ) }

# 컨텍스트 소비

컨텍스트의 제공 데이터는 useContext 훅을 사용해서 얻을 수 있었고, 이를 통해 useColoruseCount 함수를 만들어 사용했습니다.
데이터를 얻는 또 다른 방법으로 Consumer 컴포넌트를 사용할 수도 있습니다.
컨텍스트 객체만 있을 때 별도의 함수를 가져오지 않고도 Consumer 컴포넌트로 바로 데이터를 얻어 렌더링(소비)할 수 있습니다.

Consumer 컴포넌트의 내용은 함수여야 하며, 매개변수로 제공 데이터를 받습니다.

TSX
content_copy
L
L
L
1
2
3
<MyContext.Consumer> {데이터 => <div>{데이터}</div>} </MyContext.Consumer>
Consumer 컴포넌트 패턴
/src/components/A1.tsx
TSX
content_copy
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
L
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { ColorContext } from '../providers/color' import { CountContext } from '../providers/count' import A2 from './A2' export default function A1() { return ( <> <ColorContext.Consumer> {colorState => { const [color] = colorState! return <div style={{ color }}>A1</div> }} </ColorContext.Consumer> <CountContext.Consumer> {({ count }) => <div>{count}</div>} </CountContext.Consumer> <A2 /> </> ) }

# 컨텍스트 이름

React Developer Tools를 사용하면 다음과 같이 컴포넌트 구조를 확인할 수 있습니다.
여기서 .Provider 컴포넌트는 기본적으로 Context라는 이름을 사용하기 때문에 여러 컨텍스트를 구분하기 어렵습니다.

표시 이름 없음 zoom_out_map
표시 이름 없음

컨텍스트 객체에서 displayName 속성을 사용해 컨텍스트의 이름을 지정할 수 있습니다.

/src/providers/color.tsx
TSX
content_copy
L
L
L
L
1
2
3
4
// ... export const ColorContext = createContext<ColorState | null>(null) ColorContext.displayName = 'Color'
/src/providers/count.tsx
TSX
content_copy
L
L
L
L
1
2
3
4
// ... export const CountContext = createContext<Count>({} as Count) CountContext.displayName = 'Count'

그러면 다음과 같이 .Provider 컴포넌트에 해당 이름이 표시됩니다.

표시 이름 확인 zoom_out_map
표시 이름 확인