contextAPI

studying  · 5 mins read

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>
		);

references