contextAPI란
React 라이브러리에서 제공하는 상태 관리 API
Context는 ‘값’ 상태가 될수도 있고 함수, 외부 라이브러리, DOM일 수도 있다.
Context를 사용하면 값을 props로 하위 컴포넌트에 계속해서 넘겨줘야 하는 번거로움을 해결할 수 있다.
Context 만들기
import { createContxt } from "react";
const MyContext = createContext(defaultValue);
Context.Provider
Context 오브젝트에 포함된 React Component이다. Context를 구독하는 컴포넌트들에게 Context의 변화를 알린다.
<MyContext.Provider value={어떤 값} />
- 어떤 Context를 구독하고 있는 컴포넌트를 렌더링 할 때, React는 트리 상위에서 가장 가까이 있는 짝이 맞는 Provider로부터 현재값을 읽는다. (동일한 컴포넌트로부터의 Provider.)
- 어떤 Context를 구독하고 있는 컴포넌트의 상위에 Provider가 없으면 이 Context의 값으로는 Context를 만들었을 때 지정한 defaultValue값으로 설정된다.
- Provider 하위에서 Context를 구독하는 모든 컴포넌트는 Provider의 value prop이 바뀔 때마다 다시 렌더링 된다.
- Context 값이 바뀌었는지의 여부는 Object.is와 동일한 알고리즘을 사용해 비교
그렇다면, 구독은 어떻게?
Context.Consumer
Context 변화를 구독하는 React 컴포넌트. 함수형 컴포넌트 안에서 Context 구독 가능, 자식은 반드시 함수여야 한다.
<MyContext.Consumer>
{value => /* context 값을 이용한 렌더링 */}
</MyContext.Consumer>
<MyContext.Consumer>
/* context 값을 이용한 렌더링 */
{value => {
const data = value.map(valueElem => valueElem.content);
return <Post data = {data} />
}
</MyContext.Consumer>
- createContext에 보내는 defaultValue 모양을 하위 컴포넌트가 받고 있는 매개변수(Consumer 컴포넌트의 자식 함수) 모양과 동일하게 만들어야 한다.
useContext 훅을 사용해서 구독을 쉽게 할 수 있다.
useContext
import { useContext } from "react";
import { MyContext } from "contexts/MyContext";
const SubscribedComponent = () => {
const { value, setValue } = useContext(MyContext);
/* context 값 사용... */
};
Context 이름 지정하기
Context에도 이름을 지정할 수 있다 → 개발자 도구에서 지정한 이름으로 표시된다!
const MyContext = createContext(defaultValue);
MyContext.displayName = "MyDisplayName";
<MyContext.Provider> // "MyDisplayName.Provider" in DevTools!
여러 Context 값을 받는 컴포넌트
// 여러 context의 값을 받는 컴포넌트
function Content() {
return (
<Context1.Consumer>
{value1 => (
<Context2.Consumer>
{value2 => (
<Post data1={value1} data2={value2} />
)}
</Context2.Consumer>
)}
</Context1.Consumer>
);
}
⇒ 만약 둘 이상의 context 값이 함꼐 쓰이는 경우가 많다면 그냥 그 값들을 한 번에 묶어서 Context로 만들고 한 번에 값들을 받는 컴포넌트를 만들면 복잡하지 않다. 하지만 묶인 Context들 중 하나의 값이 변경될 때마다 두 Context 각각에 대해 구독되어있는 컴포넌트들이 전부 리렌더링이 된다. → 낭비!
Context 업데이트는 어떻게?
Context를 통해 메서드를 보내면 된다.
createContext에 보내는 defaultValue 모양을 하위 컴포넌트가 받고 있는 매개변수(Consumer 컴포넌트의 자식 함수) 모양과 동일하게 만들어야 한다.!
- 주의
다시 렌더링할지 여부는 value의 reference를 확인한다. ⇒ Provider가 렌더링 될 때마다 Provider의 value props도 새로 할당되기 때문에 그 하위에서 구독하고 있는 컴포넌트 모두가 다시 렌더링 된다. ⇒ Provider 부모의 state로 끌어올리기!
const ParentComponent = () => {
const [value, setValue] = useState(initialState);
return <MyContext.Provider value = >
...
</MyContext.Provider>;
};
Context가 여러 개 일 때, 하나로 묶지 않고 각각 사용하고 싶을 땐?
Context가 여러 개 일 때 아래 코드와 같이 Provider 여러 개를 넣어줘야 하므로 코드가 복잡해진다.
return (
<Context1.Provider>
<Context2.Provider>
....
</Context2.Provider>
</Context1.Provider>
)
다음과 같이 reduce, createElement 사용 → AppProvider를 만들어서 Provider를 묶어서 해결!
import React, { ReactElement } from "react";
import { ContextProvider1 } from "contexts/Context1";
// Context, ContextProvider 같이 정의되어있음
import { ContextProvider2 } from "contexts/Context2";
const AppProvider = ({ children }) => {
const providers = [ContextProvider1, ContextProvider2];
return providers.reduce(
(prev, provider) =>
React.createElement(provider, {
children: prev,
}),
children
);
};
export default AppProvider;
import AppProvider from "contexts/AppProvider";
function App(): JSX.Element {
return (
<AppProvider>
/* ... */
</AppProvider>
);