[멋쟁이사자처럼 부트캠프 프론트엔드 14기/REACT] useContext Hook

minji·2025년 9월 12일
post-thumbnail

"이 state 필요한 사람? 나한테서 바로 전달받을 사람?" 📢


리액트에서는 컴포넌트간 상태공유를 할 때, 부모 컴포넌트에서 상태관리를 하면서 자식 컴포넌트에는 상태정보를 props 로 전달한다.

다음과같이 단순한 구조에서는 상태를 공유하고 관리하는 것이 어렵지 않다.

하지만, 상태 끌어올리기 글에서도 다뤘듯이 자식 컴포넌트에서 관리중이던 state 를 부모 컴포넌트로 끌어올려 공유해야하는 상황도 있다.

상태를 끌어올리기만 하는 상황이 있는 것은 아니다. 아래와 같은 상황에서 A컴포넌트에서 C컴포넌트 로 상태를 전달하려면 중간에 C컴포넌트 에 관여하지 않는 컴포넌트에도 props 를 전달하여야 합니다.

내가 필요한 state도 아닌데 전달만하기위해서 props를 받아서 전달을 해야한다면?
물론, 하면 해! 한다면 할 말이 없지만 무척 비효율적인 처리방식이다.
이러한 처리방식을 Props Drilling 이라고한다.

✋🏻Props Drilling 이란?
props를 오로지 하위 컴포넌트로 전달하는 용도로만 사용하는 컴포넌트들을 거치면서 데이터를 전달하는 과정이다.
state를 전달해줄 상위 컴포넌트가 너무 높이 위치해있어서 하위 컴포넌트까지 props로 내려가는것이 드릴과 비슷하여 붙여진 이름이지 않을까싶다.

Props Drilling은 비효율적인 부분뿐만 아니라 코드가 복잡해지고 그로인해 유지보수가 어려워지는 문제까지 야기한다.
또한, 컴포넌트 구조가 변경되면 props 전달경로도 함께 변경되어야하므로 효율을 중시하는 개발에서는 질색팔색할 일인 것이다. 🤯


그렇다면 C컴포넌트 에서 필요한 state니까 A컴포넌트 에서 바로 전달하면 편하고 좋지않을까?!

그래서 등장한 것이 리액트의 컨텍스트(Context) 이다.
컨텍스트(Context) 를 사용하면 컨텍스트의 내부에 값을 공유할 수 있다.

컨텍스트(Context)란?

| 부모 컴포넌트가 트리 전체에 데이터를 전달할 수 있도록 해주는 것

자세히 살펴보겠지만 간단하게 context가 진행되는 단계는 다음과 같다.

  1. Context 생성
const Context = createContext(defaultValue)
  1. 데이터가 필요한 컴포넌트에서 context를 사용
// 레거시 방식 (사용권장 ❌)
function Button() {
  return (
    <ThemeContext.Consumer> // .Counsumer는 생략불가능 ❌
    	//...
    </ThemeContext.Consumer>
   )
}

// useContext 방식 (사용권장 ✅)
function Button() {
  const theme = useContext(ThemeContext)
  return <button className={theme} />
  1. 데이터를 지정하는 컴포넌트에서 context를 제공
function App() {
  const [theme, setTheme] = useState('light')
  // ...
  return (
    <ThemeContext.Provider value={theme}> // .Provider 생략가능 ✅
    	<Page />
    </ThemeContext.Provider>
}

컨텍스트의 특징

  • 정적인 값뿐만 아니라 동적인 값도 전달할 수 있다.
  • 값이 변경되면 공유받은 하위 컴포넌트들의 값도 자동으로 업데이트 된다.
  • createContext() 로 생성한 컨텍스트는 완벽하게 분리되어 있기 때문에 서로 다른 리액트 context는 서로에게 영향을 주지 않는다. → 하나의 컴포넌트에서 여러 컨텍스트를 사용 또는 제공할 수 있다.

컨텍스트가 사용되는 대표적인 예시

1. 상태관리
2. 테마관리

  • 라이트 / 다크모드 전환시, 상단에 테마 컨텍스트 공급자를 위치시키고 하위 컴포넌트에서는 정보를 읽게하면 모드 전환을 쉽게 할 수 있다.

3. 인증관리

  • 로그인한 사용자 정보는 여러 컴포넌트에서 필요할 것이다. 이 때, 컨텍스트에 사용자 정보를 저장해두고 필요한 컴포넌트에서 접근하여 사용할 수 있다.

4. 라우팅관리

이제 위에서 간단하게 살펴봤던 context 의 진행단계에 대해서 살펴보자.

1️⃣ 컨텍스트 생성

컨텍스트를 생성하여 사용하려면 먼저 createContext를 import 해야한다.
이를 통해 컨텍스트 객체를 만들 수 있는데, 생성된 컨텍스트 객체는 공급자(Provider)소비자(Consumer) 를 포함한다.

생성시 매개변수로 defaultValue를 받는데, 이 값은 상위에 일치하는 컨텍스트 제공자가 없을 경우 컨텍스트가 가지는 기본값이다.
의미있게 설정할 기본값이 없으면 null 로 설정하면되며, 이 값은 정적인 값으로 시간이 지나도 변경되지 않는다.

생성을 하면 위에서 언급한 컨텍스트 객체를 반환하게 되는데, 객체 자체는 어떠한 정보도 가지고 있지 않다.
대신 다음의 몇 가지 속성을 가지고 있다.

  • SomeContext : 컴포넌트에 컨텍스트 값을 제공한다.
  • SomeContext.Consumner : 컨텍스트 값을 읽는 방식이며, useContext가 등장하기 전의 방식이다. (legacy way)
  • SomeContext.Provider : 컨텍스트 제공자로, 컴포넌트를 감싸서 컨텍스트의 값을 모든 내부 컴포넌트에 지정한다. (legacy way)

반환된 컨텍스트 객체를 컴포넌트가 useContext()에 전달하여 컨텍스트 값을 읽을 수 있게 한다.

// 기본 형태
import { createContext } from 'react'

export const MyContext = createContext(defaultValue)
import { createContext } from 'react'

export const THEME = {
	light: {
		foreground: '#000',
		background: '#fff',
	},
	dark: {
		foreground: '#fff',
		background: '#000',	
	}
}

// 다른 컴포넌트에서 사용할 수 있도록 export 
export const ThemeContext = createContext() // createContext의 유일한 인자는 '기본값'임

console.log(ThemeContext.Provider) // 공급자
console.log(ThemeContext.Consumer) // 소비자

2️⃣ 컨텍스트 값 제공 (Provider)

컨텍스트를 제공하지 않으면 리액트는 생성단계에서 지정한 기본값을 가져온다.
생성한 컨텍스트를 자식 컴포넌트에게 제공하기 위해서는 provider 가 필요하다.

3️⃣ 컨텍스트 값 사용 (Consumer)

생성한 컨텍스트를 사용하려면 useContext 라는 리액트 훅이 필요하다.
더불어 생성한 컨텍스트를 사용하려는 컴포넌트에서는 useContext 훅과 함께 생성 컨텍스트도 필요하다.

import { useContext } from 'react'
import { ThemeContext } from '파일위치'

export default function Toolbar() {
  const [mode] = useContext(ThemeContext) // 컨텍스트 값 읽기
  // ...

⚙️ 효율적인 컨텍스트 관리

컴포넌트 내부에 작성된 컨텍스트 로직을 별도의 파일로 분리하여 사용 및 관리하는 것이 유지보수 측면에서도, 코드의 가독성 측면에서도 좋다.

  1. 별도의 파일에 사용할 컨텍스트 생성
  2. 프로바이더 컴포넌트 작성
  • 컨텍스트에 실제 값을 넣어서 하위 컴포넌트에 전달해주는 컴포넌트로, 상태와 상태 업데이트 함수를 값으로 제공한다.
  1. 프로바이더 컴포넌트 사용
  • 상태를 공유할 컴포넌트를 프로바이더 컴포넌트로 감싸준다.
  • 프로바이더 컴포넌트에 감싸진 컴포넌트와 그 하위 컴포넌트 모두 상태를 공유받게 된다.

✋🏻잠깐! 지금까지 컨텍스트에 대해 살펴봤는데 그래서 useContext가 뭔데?

useContext 란?

| 컴포넌트에서 Context 를 읽고 구독할 수 있는 리액트 훅이다.

형태

const value = useContext(SomeContext)

매개변수

  • SomeContext : createContext 로 생성한 컨텍스트로 자체적으로 정보를 담고 있지는 않다. 참고

반환값

  • 호출하는 컴포넌트에 대한 컨텍스트 값을 반환한다.
  • 트리에서 호출하는 컴포넌트 상위의 가장 가까운 SomeContext에 전달된 값으로 결정된다.
  • Provider가 없으면 반환되는 값은 생성시 설정한 defaultValue가 되며, 반환된 값은 항상 최신상태이다.
  • 컨텍스트가 변경되면 리액트는 자동으로 변경된 컨텍스트를 읽는 컴포넌트를 리렌더링한다.

useContext() 는 항상 호출하는 컴포넌트 상위에서 가장 가까운 Provider 를 찾는다. 위쪽 방향으로 찾으며, 호출하는 컴포넌트 내의 Provider 는 고려하지 않는다.

context 를 업데이트하려면 State와 함께 시너지효과를 내보자!


🔗 참고자료
https://ko.react.dev/reference/react/createContext
https://ko.react.dev/reference/react/useContext
https://ko.react.dev/learn/passing-data-deeply-with-context
https://ko.react.dev/learn/scaling-up-with-reducer-and-context#moving-all-wiring-into-a-single-file
https://kentcdodds.com/blog/prop-drilling

profile
FE Developer .mjs

0개의 댓글