완벽 가이드 - 상태 관리 심화

primav·2024년 12월 4일

React

목록 보기
24/35
post-thumbnail

📌 State 끌어올리기

동일한 데이터에 대한 변경사항을 여러 컴포넌트에 반영해야 할 때가 있다.
이럴 때는 가장 가까운 공통 부모로 state를 끌어올려야 한다.

문제점

하지만 컴포넌트들이 복잡해지고 자식 요소, 자식의 자식 등 겹겹이 쌓이면 상태를 관리하기가 어려워진다.
이 때 발생하는 문제점이 Prop Drilling이다.

📌 Prop Drilling

위의 State 를 끌어오는 방식을 사용한다면 아래와 같이 복잡한 컴포넌트가 생긴다면, 7-child의 데이터를 사용하려면 7-child → 5-child → 4-child → 1-child 이렇게 위에 있는 모든 컴포넌트들을 거치면서 끌어올려야 한다.

이때 Prop Drilling 은 props 를 오로지 하위 컴포넌트로 전달하는 용도로만 쓰이는 컴포넌트들을 거치면서 데이터를 전달하는 과정이다.

아래 사진으로 예를 들면 1-child에서 7-child 의 데이터를 사용하고 싶다면, 5-child , 4-child 에서는 데이터를 사용안해도 전달을 해야 한다는 소리이다.
( 7-child → 5-child → 4-child → 1-child )

Prop Drilling 단점

prop 전달이 많은 컴포넌트들을 거치면 해당 prop을 추적하기가 어려워지고, 유지보수가 더욱 어려워진다.

👀 그러면 어떤 해결 방법이 있을까?

  • 컴포넌트 합성
  • Context API

📌 컴포넌트 합성

특정 컴포넌트가 다른 컴포넌트를 포함하거나 조합하여 화면을 구성하는 방식이다.
Prop Drilling 문제를 완화하기 위해 사용하는 접근법 중 하나로, 컴포넌트 합성을 통해 데이터를 props로 직접 필요한 컴포넌트에 전달하거나, 공통의 Wrapper 컴포넌트를 활용하여 복잡도를 줄일 수 있다.

✔️ Prop Drilling 문제 상황
data 는 Parent에서 필요하지 않지만 Child로 전달하기 위해 거쳐야 한다.

function Grandparent() {
  const data = "Hello, World!";
  return <Parent data={data} />;
}

function Parent({ data }) {
  return <Child data={data} />;
}

function Child({ data }) {
  return <div>{data}</div>;
}

✔️ 컴포넌트 합성을 활용한 해결
parent가 불필요하게 props를 전달하지 않아도 된다.

function Grandparent() { // data를 직접 전달하는 책임은 Grandparent가 가짐
  const data = "Hello, World!";
  return ( // 이 부분에서 두 컴포넌트를 합성함
    <Parent>
      <Child data={data} />
    </Parent>
  );
}

function Parent({ children }) { // 단순히 레이아웃만 제공
  return <div className="parent-container">{children}</div>;
}

function Child({ data }) {
  return <div>{data}</div>;
}

컴포넌트 합성 문제점

  • 여전히 데이터가 여러단계의 컴포넌트를 거쳐야 한다.
    ( Grandparent → Parent → Child )

  • 구조가 복잡해질 수 있다.
    컴포넌트를 감싸는 식으로 사용을 하기 때문에 각 컴포넌트가 어떤 역할을 하는지 이해하기 어렵다.

📌 Context API

이때 context API 를 사용하게 되면 단계마다 일일이 props를 넘겨주지 않고도 컴포넌트 트리 전체에 데이터를 제공할 수 있다.

일반적인 리액트 컴포넌트 트리 안에서 전역적이라고 볼 수 있는 데이터를 공유할 수 있도록 고안된 방법이다.

Context API는 주로 어디에 사용할까?

Context API를 통해 전달하는 데이터의 종류

  • 테마 데이터 (다크 모드, 라이트 모드)
  • 사용자 데이터 (현재 인증된 사용자)
  • 언어 혹은 지역 데이터

Context API는 자주 업데이트할 필요가 없는 데이터에 사용한다.

  • Context API에서 State값을 변경하면, Provider로 감싼 모든 자식 컴포넌트들이 리렌더링되므로 전역 상태 관리를 위한 도구가 아닌, 데이터를 쉽게 전달하고 공유하기 위한 목적으로 사용하는 것이 적합하다.
  • 따라서 Context는 리액트에서 컴포넌트를 위한 전역 변수의 개념으로 생각하면 된다.

사용방법

1. Context 생성

createContext 함수를 사용하여 Context 객체를 생성한다.
Context 객체는 데이터의 기본 값을 가질 수 있다.

📁 Theme.jsx

import { createContext } from 'react';

export const ThemeContext = createContext('light'); // 기본값으로 'light' 설정

2. Provider를 사용하여 데이터 제공

Context의 데이터를 제공하기 위해 Provider 컴포넌트를 사용한다.
Provider는 데이터를 받아서 하위 트리의 모든 컴포넌트에 전달한다.

  • value 속성을 통해 하위 컴포넌트에 공유할 데이터를 전달한다.
  • 이 데이터는 객체, 문자열, 함수 등 어떤 값이라도 가능하다.
📁 App.js

import React, { useState } from 'react';
import { ThemeContext } from './ThemeContext';

function App() {
  const [theme, setTheme] = useState('light');

  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}

export default App;

3. Consumer 또는 useContext로 데이터 소비

  1. useContext 훅 사용 (권장!!)
import React, { useContext } from 'react';
import { ThemeContext } from './ThemeContext';

function Toolbar() {
  const { theme, setTheme } = useContext(ThemeContext);

  return (
    <div>
      <p>현재 테마: {theme}</p>
      <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
        테마 변경
      </button>
    </div>
  );
}

export default Toolbar;
  1. Context.Consumer 사용
import React from 'react';
import { ThemeContext } from './ThemeContext';

function Toolbar() {
  return (
    <ThemeContext.Consumer> // Consumer 로 감싸서 함수형 컴포넌트로 렌더링
      {({ theme, setTheme }) => (
        <div>
          <p>현재 테마: {theme}</p>
          <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
            테마 변경
          </button>
        </div>
      )}
    </ThemeContext.Consumer>
  );
}

export default Toolbar;

4. Context API로 여러 Context 다루기

여러 Context를 사용하는 경우 Provider를 중첩해 사용할 수 있다.

import { UserContext } from './UserContext';
import { ThemeContext } from './ThemeContext';

function App() {
  return (
    <UserContext.Provider value={{ user: 'John Doe' }}>
      <ThemeContext.Provider value={{ theme: 'dark' }}>
        <Dashboard />
      </ThemeContext.Provider>
    </UserContext.Provider>
  );
}

하위 컴포넌트에서 useContext를 사용해 각각의 데이터를 가져올 수 있다.

const user = useContext(UserContext);
const theme = useContext(ThemeContext);

0개의 댓글