Context API는 불필요한 prop-drilling을 하지 않아도 타겟 컴포넌트에게 필요한 데이터/상태를 쉽게 공유(전역 상태 관리 )할 수 있게 해준다. 즉, 모든 컴포넌트 또는 특정 컴포넌트에서 사용할 수 있는 데이터/상태를 전달할 때 사용한다.
useContext
는 createContext
로 생성한 Context를 Provider
하위 컴포넌트에서 읽고 구독할 수 있는 React Hook이다.
*prop-drilling이란? : 컴포넌트 트리에서 데이터를 하위 컴포넌트로 전달하기 위해 중간 컴포넌트를 통해 props를 내려주는 것.
i) createContext
: Context 생성
import { createContext } from 'react';
export const Context이름 = createContext();
ii) Provider
: 하위 컴포넌트에게 Context 전달
//children = Provider로 감싸는 하위 컴포넌트
export const Provider이름 = ({children}) => {
//전역으로 관리할 state, 함수 작성
return (
<Context이름.Provider value={{전역으로 관리할 state, 함수}}>
{children}
</Context이름.Provider>
}
Provider
를 Context.jsx에서 생성한 뒤 App.jsx에서 import해온다.
import { Provider이름 } from './contexts/Context.jsx';
function App() {
return (
<Provider이름>
<하위 컴포넌트 1 />
<하위 컴포넌트 2 />
<하위 컴포넌트 3 />
</Provider이름>
);
}
하위 컴포넌트 1, 2, 3은 같은 Context를 구독할 수 있다.
iii) useContext
: Context 구독, 해당 context의 현재 값 가져오기
import { useContext } from 'react';
import { Context이름 } from '../contexts/Context.jsx';
function 구독컴포넌트() {
//구조 분해 할당으로 가져온다.
const {필요한 state, 함수} = useContext(Context이름)
...이하 생략
}
예시로 Input.jsx
에 입력한 내용을 DeepStructure.jsx
의 하위 컴포넌트인 MessageDisplay.jsx
에서 띄우는 앱을 만들어보자. 파일의 구조는 이런 식으로 구성될 것이다.
//파일 트리
App
├── Input
├── DeepStructure
│ └── Level1
│ └── MessageDisplay
prop-drilling 방법으로 앱을 구성한다면 App.jsx
에서 state에 관련된 변수와 함수를 선언한 뒤, props로 내려주어야 한다. 하지만 이 과정에서 DeepStructure
와 Level1
은 state를 사용하지 않음에도 전달을 위해 props를 받아야한다. Context API를 사용한다면 이런 불필요한 props 전달 없이 가능하다!
파일 위치: 📁Context - MessageContext.jsx
---------------------------------------
import { useState } from 'react';
import { createContext } from 'react';
//context 생성
export const MessageContext = createContext();
//provider 생성
export function MessageProvider({ children }) {
const [message, setMessage] = useState('');
return (
<MessageContext.Provider
value={{
message,
setMessage,
}}
>
{children}
</MessageContext.Provider>
);
}
파일 위치: 📁src - App.jsx
------------------------
import Input from './components/Input.jsx';
import DeepStructure from './components/DeepStructure.jsx';
import { MessageProvider } from './contexts/MessageContext.jsx';
function App() {
return (
<MessageProvider>
<Input />
<DeepStructure />
</MessageProvider>
);
}
파일 위치: 📁components - Input.jsx
---------------------------------
import { useContext, useState } from 'react';
import { MessageContext } from '../contexts/MessageContext.jsx';
function Input () {
const {setMessage} = useContext(MessageContext)
const [text, setText] = useState('')
const handleSubmit = (e) => {
e.preventDefault()
setMessage(text)
setText('')
}
return (
<form onSubmit={handleSubmit}>
<input type='text'
value={text}
onChange={(e) => setText(e.target.value)}
/>
<button type='submit'>확인</button>
</form>
)
}
export default Input
아래 예시가 prop-drilling을 하지 않아도 된다는 Context API의 장점이 가장 잘 드러난다.
파일 위치: 📁components - DeepStructure.jsx
-----------------------------------------
import { useContext } from 'react';
import { MessageContext } from '../contexts/MessageContext.jsx';
function DeepStructure() {
return (
<div>
<Level1 />
</div>
);
}
function Level1() {
return (
<div>
<MessageDisplay />
</div>
);
}
function MessageDisplay() {
const { message } = useContext(MessageContext);
return (
<div>
<h3>메시지 표시 영역</h3>
<p>{message || '아직 메시지가 없습니다!'}</p>
</div>
);
}
export default DeepStructure
prop-drilling의 문제를 해결하고자 나온 개념으로, 데이터/상태를 전역으로 관리하여 어느 컴포넌트에서도 접근하도록 해주는 것이다. 즉, 지역/컴포넌트 간 상태 관리와 달리, props를 전달하지 않아도 Context API나 Redux 등을 통해 데이터/상태에 접근할 수 있다.
*지역 상태 관리란? : 특정 컴포넌트 안에서만 관리되는 상태. 다른 컴포넌트들과 데이터를 공유하지 않는다.
*컴포넌트 간 상태 관리란? : 전역 상태 관리의 상대개념으로, 여러가지 컴포넌트에서 관리되는 상태. 보통 상위 컴포넌트에서 하위 컴포넌트로 props를 넘겨 전달한다.
중간 컴포넌트에 불필요한 props 전달
중간 컴포넌트를 통해 불필요한 props 전달은 불필요한 복잡성을 초래한다.
오류 발생 시 추적 어려움
props가 전달되지 않은 상황, props 이름이 중간 계층에서 변경되는 상황 등 전달 과정에서 발생하는 오류의 다양한 원인을 추적하고 발견하는 것이 어렵다.
props 데이터 형식 변경 불편함
props의 데이터 형식을 변경하는 경우, 컴포넌트 계층 전체에서 업데이트해야 하기 때문에 어렵다.
결론은 아니다! prop-drilling이 가진 장점도 있고 Context API가 가진 한계도 분명하기 때문에, 때에 따라서 적절히 사용해야한다.
prop-drilling을 사용할 때
-props를 전달하는데 depth가 깊지 않은 경우
-데이터의 흐름이 명확하게 보여야 하는 경우
전역 상태 관리(Context API, redux 등)을 사용할 때
-자주 업데이트할 필요가 없는 데이터(테마 데이터, 사용자 데이터, 언어/지역 데이터 등)를 관리할 경우
-데이터/상태를 여러 곳에서 공유해야 하는 경우
Provider
에서 제공한 value, 즉 state값이 변경되면 Provider
의 모든 하위 컴포넌트가 전부 리렌더링 된다. 이는 성능 측면에서 문제가 발생할 수 있다.
이를 해결하기 위해 state를 여러 개의 Context로 분리하거나 Redux 등 전역 상태 라이브러리를 사용, memoization 기법을 사용한다.
useContext 공식 문서
Context API 참고 사이트 1
prop-drilling 참고 사이트 1