React의 Context와 Provider

FE_Sujin·2024년 5월 2일
1

React

목록 보기
4/8
post-thumbnail

Context 란?

💡 Context
: props 없이도 부모 컴포넌트에서 자식 컴포넌트로 정보(데이터)를 깊이 전달할 수 있게 해준다

보통 React에서는 부모 컴포넌트에서 자식 컴포넌트로 props를 통해 정보를 전달한다.

그러나 중간에 많은 컴포넌트를 거쳐야 하거나, 애플리케이션의 많은 컴포넌트에서 동일한 정보가 필요한 경우에는 props를 전달하는 것이 번거롭고 불편할 수 있다.

이때 Context를 사용하면 명시적으로 props를 전달해주지 않아도 부모 컴포넌트가 트리에 있는 어떤 자식 컴포넌트에서나 정보를 사용할 수 있다.


Props 전달하기의 문제점

props 전달하기는 UI 트리를 통해 명시적으로 데이터를 사용하는 컴포넌트에 전달하는 훌륭한 방식이다.

그러나 어떤 prop을 트리를 통해 깊이 전달해야 하거나, 많은 컴포넌트에서 같은 prop이 필요한 경우 props 전달 방식은 장황하고 불편할 수 있다.

데이터가 필요한 여러 컴포넌트의 가장 가까운 공통 부모 컴포넌트가 트리 상 높이 위치할 경우, 높게까지 state를 끌어올리는 것"Prop drilling"이라는 상황을 초래할 수 있다.

*state를 끌어올린다는 것은...
: 두 컴포넌트의 state가 항상 함께 변경되기를 원한다면, 각 컴포넌트에서 state를 제거하고 가장 가까운 공통의 부모 컴포넌트로 옮긴 후 props로 전달해야 한다. 이 방법을 State 끌어올리기라고 한다.

Props drilling 이 왜 문제가 될까?

예를 들어 보자. props를 전달할 때 5개, 10개 등의 컴포넌트를 거쳐야 한다면 어떻게 될까?

코드를 읽을 때 해당 prop을 추적하기 힘들고, 유지보수도 어려워질 것이다.

이 데이터를 props로 전달하지 않고 순간이동시키는 방법이 Context 이다!

Context를 사용하면 컴포넌트가 멀리 있는 부모 컴포넌트로부터 props로 전달받지 않으면서 정보를 받을 수 있다.


Context 사용 방법

Context는 다음의 세 단계로 사용할 수 있다.

1. Context를 생성한다.

2. 데이터가 필요한 컴포넌트에서 context를 사용한다.

3. 데이터를 지정하는 컴포넌트에서 context를 제공한다.

아래 단계별로 예시를 든 코드를 보면 이해하기 쉽다.

먼저, 아래 코드에서 <Section> 컴포넌트에 level prop을 모든 <Heading> 에 전달하고 싶다고 가정하자.

<Section level={3}>
  <Heading>About</Heading>
  <Heading>Photos</Heading>
  <Heading>Videos</Heading>
</Section>

1단계: Context를 생성한다

먼저 다음과 같이 React의 createContext 를 이용해 컴포넌트에서 사용할 수 있는 context 를 만들어야 한다.

import { createContext } from 'react';

export const LevelContext = createContext(1);

createContext의 유일한 인자는 기본값이다.


2단계: Context를 사용한다

React에서 useContext Hook을 가져온다.

그리고 Heading 컴포넌트 안에서 LevelContext context 에서 값을 읽어준다.

import { useContext } from 'react';
import { LevelContext } from './LevelContext.js';

const Heading = ({ children }) => {
	const level = useContext(LevelContext);     // ◀️
  	
  	// ... 
}

아직까지는 Context를 제공하는 로직이 없기 때문에 모든 <Heading> 컴포넌트가 이전 단계에서 지정한 기본값을 사용한다.

즉, 1단계에서 1createContext의 인수로 지정했으므로, (const LevelContext = createContext(1);) useContext(LevelContext)1을 반환한다.


3단계: Context를 제공한다

Section 컴포넌트는 자식들을 렌더링하고 있는데, LevelContext를 자식들에게 제공하기 위해 context provider로 감싸준다.

import { LevelContext } from './LevelContext.js';

const Section = ({ level, children }) => {
  return (
    <section className="section">
      <LevelContext.Provider value={level}>  // ◀️
      	{children}
      </LevelContext.Provider>
    </section>
  );
}

위 예시 코드의 ◀️ 가 표기된 부분은 React에게 Section 내의 어떤 컴포넌트가 LevelContext를 요구하면 level을 주라고 알려준다.

💡 주의할 점
: React는 value 을 받는 provider 로부터 시작해서 특정 context 를 사용하는 모든 자식들을 자동으로 리렌더링한다.


위 과정을 간략히 정리하면 다음과 같다.

1. level prop 을 <Section>에 전달한다.

2. Section은 자식을 <LevelContext.Provider value={level}>로 감싸준다.

3. Heading은 useContext(LevelContext) 를 사용해 가장 근처의 LevelContext 의 값을 요청한다.

useContext, createContext 에 대해

1. useContext

useContext는 context 를 읽고 구독해주는 React Hook이다.

컴포넌트의 최상위 수준에서 호출하여 context 를 읽고 구독하며, 전달한 context 에 대한 context value 를 반환한다.

import { useContext } from 'react';

function Button() {
	const theme = useContext(ThemeContext);
}

context 값을 결정하기 위해 React 는 컴포넌트 트리를 검색하고 특정 context 에 대해 상위에서 가장 가까운 context provider 를 찾는다.

💡 주의할 점
: userContext() 는 항상 호출하는 컴포넌트 상위에서 가장 가까운 provider 를 찾는다. useContext() 를 호출하는 컴포넌트 안의 provider는 고려하지 않는다.


2. createContext

createContext를 사용해 context를 제공하거나 읽을 수 있다.

const SomeContext = createContext(defaultValue); 

매개변수

  • 기본값 : 컴포넌트가 컨텍스트를 읽을 때 상위에 일치하는 컨텍스트 제공하자 없는 경우 컨텍스트가 가져야 할 값이며, "최후의 수단"으로 사용된다. 보통 의미 있는 기본값이 없으면 null로 지정한다.

3. SomeContext.Provider

컴포넌트를 컨텍스트 제공자로 감싸서 이 컨텍스트의 값을 모든 내부 컴포넌트에 지정한다.

const App = () => {
	const [theme, setTheme] = useState('light');
  
  	// ...
  	return (
    	<ThemeContext.Provider value={theme}>
        	<Page />
      	</ThemeContext.Provider>
    )
}

Props

  • value : 이 제공자 내부의 컨텍스트를 읽는 모든 컴포넌트에 전달하려는 값이다. 컨텍스트 값은 어떠한 유형이든 될 수 있다.

Context 사용하기 전 고려할 것

1. Props 전달하기로 시작하기

여러 개의 props가 여러 컴포넌트를 거쳐 가는 것은 그리 이상한 일이 아니다.

번거로운 일처럼 느껴질 수 있지만 props 전달하는 방식은 어떤 컴포넌트가 어떤 데이터를 사용하는지 매우 명확히 보여준다.

데이터 흐름이 props를 통해 분명해져 유지보수 하기에도 좋다.

2. 컴포넌트를 추출하고 JSX를 childrend으로 전달하기

데이터를 사용하지 않는 중간 컴포넌트 층을 children을 prop으로 받으면 데이터를 지정하는 컴포넌트와 데이터가 필요한 컴포넌트 사이의 층수를 줄일 수 있다.

예를 들어, 데이터 사용하지 않는 중간 컴포넌트 <Layout>이 있다면, <Layout data={data} />와 같이 데이터를 prop으로 전달하지 않고, 대신 <Layout>children을 prop으로 받고 <Layout><Component data={data} /></Layout>을 렌더링해준다.


위 두 가지 방법이 맞지 않다고 판단되는 경우에 context를 사용하자!


Context 사용 예시

  • 테마 지정하기
    : 다크 모드와 같이 사용자가 애플리케이션 모양을 변경할 수 있는 경우 context provider를 앱 최상단에 두고 시각적으로 조정이 필요한 컴포넌트에 context를 사용한다.

  • 현재 계정
    : 로그인한 사용자를 알아야 하는 컴포넌트가 많을 경우, Context에 놓으면 트리 어디에서나 편하게 알아낼 수 있다.

  • 라우팅
    : 대부분의 라우팅 솔루션은 현재 경로를 유지하기 위해 내부적으로 context를 사용한다. 이것이 모든 링크의 활성화 여부를 알 수 있는 방법이다.

  • 상태 관리
    : 애플리케이션이 커지면 결국 앱 상단에 수많은 state가 생기게 된다. 아래 멀리 떨어진 많은 컴포넌트까지 값을 전달하기 위해 Context를 사용하면 복잡한 state를 쉽게 관리할 수 있다.


요약

  • Context는 컴포넌트가 트리 상 아래에 위치한 모든 곳에 데이터를 제공하도록 한다.

  • Context를 전달하기 위한 단계는 다음과 같다.

    1. export const MyContext = createContext(defaultValue)로 context를 생성하고 내보낸다.
    2. useContext(MyContext) 와 같이 Context를 useContext Hook에 전달해 자식 컴포넌트가 얼마나 깊이 있든 읽을 수 있도록 한다.
    3. 자식을 <MyContext.Provider value={...}>로 감싸 부모로부터 context를 받도록 한다.
  • Context는 중간의 어떤 컴포넌트도 지나갈 수 있다.

  • Context를 사용하기 전에 props를 전달하거나 JSX를 children으로 전달하는 것을 먼저 시도해보는 것이 좋다.


마치면서

왜 진즉 React 공식 문서를 읽으면서 공부하지 않았을까 하는 생각이 들 만큼 리액트는 공식 문서가 정말 친절하게 잘 정리되어 있고, 이해하기 쉽게 예제 코드들도 제시해준다. 굿!!


참고

profile
안녕하세요 :)

0개의 댓글

관련 채용 정보