Hook Series 4 : useContext와 전역 상태 관리⛓️‍💥

밍갱·2025년 2월 11일
0

REACT

목록 보기
8/9

1. useContext🕹️

01. useContext와 Context API 란?

Context API는 불필요한 prop-drilling을 하지 않아도 타겟 컴포넌트에게 필요한 데이터/상태를 쉽게 공유(전역 상태 관리 )할 수 있게 해준다. 즉, 모든 컴포넌트 또는 특정 컴포넌트에서 사용할 수 있는 데이터/상태를 전달할 때 사용한다.
useContextcreateContext로 생성한 Context를 Provider 하위 컴포넌트에서 읽고 구독할 수 있는 React Hook이다.

*prop-drilling이란? : 컴포넌트 트리에서 데이터를 하위 컴포넌트로 전달하기 위해 중간 컴포넌트를 통해 props를 내려주는 것.

02. Context API 사용법

i) createContext : Context 생성

import { createContext } from 'react';

export const Context이름 = createContext();

ii) Provider : 하위 컴포넌트에게 Context 전달

//children = Provider로 감싸는 하위 컴포넌트
export const Provider이름 = ({children}) => {
	//전역으로 관리할 state, 함수 작성
    
    return (
    	<Context이름.Provider value={{전역으로 관리할 state, 함수}}>
        	{children}
        </Context이름.Provider>
}

Provider를 Context.jsx에서 생성한 뒤 App.jsx에서 import해온다.

import { Provider이름 } from './contexts/Context.jsx';

function App() {
  return (
    <Provider이름>
      <하위 컴포넌트 1 />
      <하위 컴포넌트 2 />
      <하위 컴포넌트 3 />
    </Provider이름>
  );
}

하위 컴포넌트 1, 2, 3은 같은 Context를 구독할 수 있다.

iii) useContext : Context 구독, 해당 context의 현재 값 가져오기

import { useContext } from 'react';
import { Context이름 } from '../contexts/Context.jsx';

function 구독컴포넌트() {
	//구조 분해 할당으로 가져온다.
	const {필요한 state, 함수} = useContext(Context이름)
    
    ...이하 생략
}

03) Context API로 리팩토링 해보기

예시로 Input.jsx에 입력한 내용을 DeepStructure.jsx의 하위 컴포넌트인 MessageDisplay.jsx에서 띄우는 앱을 만들어보자. 파일의 구조는 이런 식으로 구성될 것이다.

//파일 트리
App
├── Input
├── DeepStructure
│   └── Level1
│       └── MessageDisplay

prop-drilling 방법으로 앱을 구성한다면 App.jsx에서 state에 관련된 변수와 함수를 선언한 뒤, props로 내려주어야 한다. 하지만 이 과정에서 DeepStructureLevel1은 state를 사용하지 않음에도 전달을 위해 props를 받아야한다. Context API를 사용한다면 이런 불필요한 props 전달 없이 가능하다!

  • Context 생성하기
파일 위치: 📁Context - MessageContext.jsx
---------------------------------------
import { useState } from 'react';
import { createContext } from 'react';

//context 생성
export const MessageContext = createContext();

//provider 생성
export function MessageProvider({ children }) {
  const [message, setMessage] = useState('');

  return (
    <MessageContext.Provider
      value={{
        message,
        setMessage,
      }}
    >
      {children}
    </MessageContext.Provider>
  );
}
  • Provider로 Context를 공유할 컴포넌트 지정
파일 위치: 📁src - App.jsx
------------------------
import Input from './components/Input.jsx';
import DeepStructure from './components/DeepStructure.jsx';
import { MessageProvider } from './contexts/MessageContext.jsx';

function App() {
  return (
	<MessageProvider>
    	<Input />
    	<DeepStructure />
    </MessageProvider>
  );
}
  • Context 구독하기
    부모 컴포넌트에서 내려주지 않아도 형제 컴포넌트끼리 데이터/상태를 공유할 수 있다.
파일 위치: 📁components - Input.jsx
---------------------------------
import { useContext, useState } from 'react';
import { MessageContext } from '../contexts/MessageContext.jsx';

function Input () {
	const {setMessage} = useContext(MessageContext)
    const [text, setText] = useState('')
    
    const handleSubmit = (e) => {
    	e.preventDefault()
        setMessage(text)
        setText('')
        }
    
	return (
    	<form onSubmit={handleSubmit}>
        	<input type='text'
            	   value={text}
                   onChange={(e) => setText(e.target.value)}
             />
             <button type='submit'>확인</button>
        </form>
    )
}

export default Input

아래 예시가 prop-drilling을 하지 않아도 된다는 Context API의 장점이 가장 잘 드러난다.

파일 위치: 📁components - DeepStructure.jsx
-----------------------------------------
import { useContext } from 'react';
import { MessageContext } from '../contexts/MessageContext.jsx';

function DeepStructure() {
  return (
  	<div>
      <Level1 />
    </div>
  );
}

function Level1() {
  return (
  	<div>
      <MessageDisplay />
    </div>
  );
}

function MessageDisplay() {
  const { message } = useContext(MessageContext);
  
  return (
    <div>
      <h3>메시지 표시 영역</h3>
      <p>{message || '아직 메시지가 없습니다!'}</p>
    </div>
  );
}

export default DeepStructure

2. 전역 상태 관리🕸️

01. 전역 상태 관리란?

prop-drilling의 문제를 해결하고자 나온 개념으로, 데이터/상태를 전역으로 관리하여 어느 컴포넌트에서도 접근하도록 해주는 것이다. 즉, 지역/컴포넌트 간 상태 관리와 달리, props를 전달하지 않아도 Context API나 Redux 등을 통해 데이터/상태에 접근할 수 있다.

*지역 상태 관리란? : 특정 컴포넌트 안에서만 관리되는 상태. 다른 컴포넌트들과 데이터를 공유하지 않는다.

*컴포넌트 간 상태 관리란? : 전역 상태 관리의 상대개념으로, 여러가지 컴포넌트에서 관리되는 상태. 보통 상위 컴포넌트에서 하위 컴포넌트로 props를 넘겨 전달한다.

02. prop-drilling의 단점

  • 중간 컴포넌트에 불필요한 props 전달
    중간 컴포넌트를 통해 불필요한 props 전달은 불필요한 복잡성을 초래한다.

  • 오류 발생 시 추적 어려움
    props가 전달되지 않은 상황, props 이름이 중간 계층에서 변경되는 상황 등 전달 과정에서 발생하는 오류의 다양한 원인을 추적하고 발견하는 것이 어렵다.

  • props 데이터 형식 변경 불편함
    props의 데이터 형식을 변경하는 경우, 컴포넌트 계층 전체에서 업데이트해야 하기 때문에 어렵다.

03. 그럼 항상 전역 상태 관리만 사용해야하나?

결론은 아니다! prop-drilling이 가진 장점도 있고 Context API가 가진 한계도 분명하기 때문에, 때에 따라서 적절히 사용해야한다.

  • prop-drilling을 사용할 때
    -props를 전달하는데 depth가 깊지 않은 경우
    -데이터의 흐름이 명확하게 보여야 하는 경우

  • 전역 상태 관리(Context API, redux 등)을 사용할 때
    -자주 업데이트할 필요가 없는 데이터(테마 데이터, 사용자 데이터, 언어/지역 데이터 등)를 관리할 경우
    -데이터/상태를 여러 곳에서 공유해야 하는 경우

04. Context API의 한계

Provider에서 제공한 value, 즉 state값이 변경되면 Provider의 모든 하위 컴포넌트가 전부 리렌더링 된다. 이는 성능 측면에서 문제가 발생할 수 있다.
이를 해결하기 위해 state를 여러 개의 Context로 분리하거나 Redux 등 전역 상태 라이브러리를 사용, memoization 기법을 사용한다.

useContext 공식 문서
Context API 참고 사이트 1
prop-drilling 참고 사이트 1

profile
미술 전공에서 프론트엔드 개발까지

0개의 댓글