상태관리의 근본 API, Context

Vue로 처음 프론트를 접한 나는 리액트도 당연히 양방향으로 데이터 이동이 가능한 줄 알았다

 

리액트를 배웠을 때 props 전달에서 데이터를 내리고 올리다가 길을 잃었다..

 

완전 냄새나는 코드를 작성하다가 Context를 알게되었다

그리고 많은 라이브러리에서 Context 개념을 사용하고 있었다!


Context란 ?

props 없이 React에서 컴포넌트 간에 데이터를 전달하기 위한 개념이다

즉 데이터를 사용하지 않는 컴포넌트들은 지나치고, 필요한 곳에서 바로 사용할 수 있다

 

리액트에서 유일하게 컴포넌트를 건너뛰면서 데이터를 전달하는 방법!

 

언제 사용하는건데?

리액트는 단방향 데이터로, 데이터를 전달하기 위해서는 부모 컴포넌트에서 자식 컴포넌트로 props 전달한다

하지만 앞서 예시처럼 중간에 여러 컴포넌트들을 거쳐야 하거나

다크모드, 전체 언어, 특정 state처럼 여러 컴포넌트들이 같은 정보를 요청할 때 전달 과정 자체가 복잡해진다 

 

이때 등장한 구세주가 Context 

Context는 위치를 넘어 컴포넌트를 뛰어 넘을 수 있는 데이터 모음으로,

Context.Provider로 감싸진 컴포넌트의 자식 컴포넌트들은  Context에 접근 할 수 있다

 

부모 컴포넌트에 Context.Provider 우산을 씌우면, 트리 아래 컴포넌트들은 Context의 정보를 사용할 수 있다!

context를 통해 파고파고 props를 전달하는 Prop drilling을 해결할 수 있다 👍

 

어떻게 사용 하는 건데?

방법은 간단하다 

1. context 객체를 만들고,
2. Provider로 사용할 컴포넌트의 부모 컴포넌트를 감싸고
3. Provider의 value로 공유할 값을 전달해주면 된다

 

예제로 살펴보자

import React from 'react';
import { useState, useContext, createContext } from 'react';
export const UserContext = createContext(); //context 생성

export default function App() {
  const [data, setData] = useState(100);
  const handleClick = () => setData((prev)=> prev+1);
  return (
    <UserContext.Provider value={{data, txt:"문자열", onClick : handleClick}}>
      <User />
    </UserContext.Provider>
  )
}
function User() {
  const {data, txt, onClick} = useContext(UserContext); // context 가져오기
  return (
      <>
        <h1>{data}</h1>
        <p>{txt}</p>

        <button onClick={onClick}>BTN</button>
      </>
      
  )
}

 

1. export const UserContext = createContext();

context를 생성한다

 

컴포넌트는 다른 파일에 있기 때문에 export 로 내보낸다

참고로 리액트에서 components 폴더처럼 context폴더를 따로 만들어서 관리하는 것이 좋다

 

2. <UserContext.Provider>

App 컴포넌트에서는 UserContext.Provider를 사용하고 있다

 

Provider로 감싸진 컴포넌트들은 value로 전달된 값에 접근 가능하다

즉, "감싼 컴포넌트 안에서 Context를 요청하면 Context를 제공해라" 라는 의미다

그러면 가장 가까운 Provider의 Context를 사용한다

 

3. <UserContext.Provider value={{data, txt:"문자열", onClick}}>

여기서 value는 트리 전체에 공유할 데이터를 value prop으로 객체에 담아 전달한다

예제에서는 state, 문자열, 함수를 담아 전달했다

 

그렇다고 모든 데이터를 하나의 context에 전달하는 것이 아닌 사용에 따라 적절한 데이터를 넣은 context를 전달해야한다

 

4. import { useContext } from 'react'

    const {data, txt, onClick} = useContext(UserContext)

이제 Context를 사용할 컴포넌트에서 작성한다

먼저 useContext hook을 import 후, 사용할 Context를 명시해야한다

 

여기서 구조분해할당으로 바로 가져오면 더욱 편하겠죠?

 

쫌 더 개선해볼까?

위 예제에서는 App 컴포넌트에서 매번 context와 Provider를 생성하고 데이터를 넣어야했다

Context에 어떤 데이터가 들어가고, 어떻게 변하는지는 솔직히 관심없다

자식 컴포넌트는 데이터를 사용하기만 하면되기때문에!

 

그렇다면 이를 분리해보자

import { useState, createContext } from "react";

export const UserContext = createContext();

export function UserContextProvider({ children }) {
  const [data, setData] = useState(100);
  const handleClick = () => setData((prev) => prev + 1);
  return (
    <UserContext.Provider value={{ data, txt: "문자열", onClick: handleClick }}>
      {children}
    </UserContext.Provider>
  );
}

앞서 context와 provider를 생성하고, 처리하는 과정을 따로 분리했다

 

그럼 변한 결과는???

import React from 'react';
import {useContext} from 'react';
import {UserContext, UserContextProvider} from './Context';

export default function App() {
  return (
    <UserContextProvider>
      <User />
    </UserContextProvider>
  )
}
function User() {
  const {data, txt, onClick} = useContext(UserContext); // context 가져오기
  return (
      <>
        <h1>{data}</h1>
        <p>{txt}</p>

        <button onClick={onClick}>BTN</button>
      </>
      
  )
}

편-안

이제 UserContextProvider안에 들어오는 컴포넌트들은 모두 children으로 들어가고

UserContext.Provider로 감싸진 컴포넌트들이 반환된다

 

Context 사용 시 주의 사항

1. context의 재정의

Context lets you write components that “adapt to their surroundings” and display themselves differently depending on where (or, in other words, in which context) they are being rendered.

 Context를 사용하면 "주변 환경에 적응"하고 렌더링되는 위치 (즉, 어떤 컨텍스트)에 따라 다르게 표시되는 컴포넌트를 작성할 수 있습니다.

 

createContext로 생성된 context는 완전히 서로 분리된 다른 컴포넌트이다

컴포넌트는 자신의 위에 있는 UI 트리에서 가장 가까운 Context.Provider의 값을 사용한다

 

그래서 만약 여러개의 context를 만들었다면 어떻게 해야하나요?

context를 재정의하는 유일한 방법은 children을 다른 값으로 context provider로 감싸는 것입니다.

 

2. 트리 전체의 재렌더링

무턱대고 Context를 props의 대체제로 남발하면 안된다

 

context provider에 객체를 내려주고 있고, 객체의 프로퍼티가 업데이트된다면 무슨 일이 일어날까?

 

Context가 갱신되면 우산을 쓰고있는 모든 자식 컴포넌트들은 re-rendering이 되기때문에

그러니 빈번하게 상태가 업데이트 되는 경우는 사용하면 안된다

 

전체적인 상태 관리를 위해 사용하자

 

Context를 사용하기 전에 고려해보기

1. 전역적으로 사용하거나 props로 전달 시작

어떤 컴포넌트가 어떤 데이터를 사용하는지 매우 명확해진다

 

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

<Layout posts={posts} />

🔽

<Layout> <Posts posts={posts} /> </Layout>

 

- 데이터를 지정하는 컴포넌트와 데이터를 필요로 하는 컴포넌트 사이 레이어 수가 줄어듬

- props를 직접 사용하지 않는 시각적 컴포넌트를 children으로 만들자

 

 

실습해보기

역시 공식문서 god

https://react.dev/learn/passing-data-deeply-with-context#challenges

 

Passing Data Deeply with Context – React

The library for web and native user interfaces

react.dev