본문 바로가기
Frontend/React

[React] Context API

by 디스코비스킷 2023. 12. 28.
반응형

Context API는 리액트 프로젝트에서 전역적으로 사용할 데이터가 있을때 유용한 기능이다. 사용자 로그인정보, 애플리케이션 환경 정보, 테마 등 여러 종류가 있다. 리액트 관련 라이브러리에서도 많이 사용되고 있다. 예를들어 리덕스, 리액트 라우터, styled-components 등의 라이브러리는 Context API 기반으로 구현되어 있다.

Context API를 사용한 전역 상태 관리 흐름 이해하기

컴포넌트간의 데이터를 props로 전달하기 위해 컴포넌트 여기저기서 필요한 데이터가 있을때는 주로 최상위 컴포넌트인 App의 state에 넣어서 관리했다. 그리고 App이 지니고 있는 state를 여러 컴포넌트를 거쳐서 전달해야했다. 이런 방식을 사용하면 유지보수성이 낮아진다.

*Context API를 사용하면 Context를 만들어 단 한번에 원하는 값을 받아와서 사용가능하다. *

리덕스MobX같은 상태관리 라이브러리를 사용하면 전역상태관리작업을 더 편하게 처리할 수 있다. 리액트 16.3버전 업데이트 후에는 Context API가 많이 개선되어서 별도의 라이브러리를 사용하지 않아도 전역상태를 손쉽게 관리할 수 있다.

Context API 사용법 익히기

color.js

import { createContext } from 'react'

const ColorContext = createContext({ color: 'black' });

export default ColorContext;

ColorBox.jsx

import React from 'react'
import ColorContext from '../contexts/color'

const ColorBox = () => {
  return (
    <ColorContext.Consumer>
        {value => (
            <div 
                style={{
                    width: '64px',
                    height: '64px',
                    background: value.color
                }}
            />
        )}
    </ColorContext.Consumer>
  )
}

export default ColorBox

App.jsx

import { useState } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'
import ColorBox from './components/ColorBox'
import ColorContext from './contexts/color'

function App() {
  const [count, setCount] = useState(0)

  return (
    // Provider를 사용할때는 value값을 꼭 명시해주어야 제대로 작동한다.
    <ColorContext.Provider value={{ color: 'red' }}>
      <div>
        <ColorBox />
      </div>
    </ColorContext.Provider>
  )
}

export default App

동적 Context 사용하기

Context값을 업데이트해야하는 경우 어떻게 해야할가??

Context 파일 수정하기

color.js

import { createContext } from 'react'

const ColorContext = createContext({
    state: {color: 'black', subColor: 'red'},
    actions: {
        setColor: () => {},
        setSubColor: () => {}
    }
});

const ColorProvider = ({ children }) => {
    const [color, setColor] = useState('black');
    const [subColor, setSubColor] = useState('red');

    const value = {
        state: { color, subColor },
        actions: { setColor, setSubColor }
    };

    return (
        <ColorContext.Provider value={value}>{children}</ColorContext.Provider>
    )    
}

// const ColorConsumer = ColorContext.Consumer; 와 같은 의미
const { Consumer: ColorConsumer } = ColorContext;

// ColorProvider, ColorConsumer 내보내기
export { ColorProvider, ColorConsumer };

export default ColorContext;

Context value에는 무조건 상태값만 있어야 하는것은 아니다. 함수를 전달해줄수도 있다.
ColorProvider라는 컴포넌트를 새로 작성했다. 거기 안에서는 ColorContext.Provider를 렌더링하고있다. 이 Provider의 value에는 상태는 state로, 업데이트 함수는 actions로 묶어서 전달하고있다. Context에서 값을 동적으로 사용할때 반드시 묶어서 줄필요는 없지만, 이렇게 state와 actions 객체를 따로 분리해주면 나중에 다른 컴포넌트에서 Context를 사용할때 편하다.
*createContext를 사용할때 기본값 객체도 실제 Provider의 value에 넣는 객체의 형태와 일치시켜주는것이 좋다. * 그러면 Context 코드를 볼때 어떻게 구성되어있는지 파악하기 쉽고, 실수로 Provider를 사용하지 않았을때 에러가 발생하지 않는다.

새로워진 Context 프로젝트에 반영

App.jsx

import ColorBox from './components/ColorBox'
import { ColorProvider } from './contexts/color'

function App() {
  return (
    // Provider를 사용할때는 value값을 꼭 명시해주어야 제대로 작동한다.
    <ColorProvider>
      <div>
        <ColorBox />
      </div>
    </ColorProvider>
  )
}

export default App

ColorBox.jsx

import React from 'react'
import { ColorConsumer } from '../contexts/color'

const ColorBox = () => {
  return (
    <ColorConsumer>
      {(value) => (
        <div
          style={{
            width: "64px",
            height: "64px",
            background: value.color,
          }}
        />
      )}
    </ColorConsumer>
  );
}

export default ColorBox

ColorBox.jsx 수정: 객체 비구조화 할당 문법

import React from 'react'
import { ColorConsumer } from '../contexts/color'

const ColorBox = () => {
  console.log("test")
  return (
    <ColorConsumer>
      {({ state }) => (
        <>
          <div
            style={{
              width: "64px",
              height: "64px",
              background: state.color,
            }}
          />
          <div
            style={{
              width: "32px",
              height: "32px",
              background: state.subColor,
            }}
          />
        </>
      )}
    </ColorConsumer>
  );
}

export default ColorBox

색상 선택 컴포넌트 만들기

SelectColors.jsx

import React from 'react'
import { ColorConsumer } from '../contexts/color';

const colors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet'];

const SelectColors = () => {
  return (
    <div>
      <h2>색상을 선택하세요. </h2>
      <ColorConsumer>
        {({ actions }) => (
          <div style={{ display: "flex" }}>
            {colors.map((color) => (
              <div
                key={color}
                style={{
                  background: color,
                  width: "24px",
                  height: "24px",
                  cursor: "pointer",
                }}
                onClick={() => actions.setColor(color)}
                onContextMenu={e => {
                    e.preventDefault(); // 마우스 오른쪽 클릭시 메뉴가 뜨는것 무시
                    actions.setSubColor(color);
                }}
              />
            ))}
          </div>
        )}
      </ColorConsumer>
    </div>
  );
}

export default SelectColors

업로드중..

기본 클릭하면 setColor가 되고
오른쪽 마우스로 클릭하면 setSubColor가 된다.

이때 오른쪽 마우스 클릭 이벤트는 onContextMenu를 사용하고, 브라우저 원래 메뉴는 e.preventDefault()로 막는다.

Consumer 대신 Hook 또는 static contextType사용하기

Context에 있는 값을 사용할때 Consumer 대신 다른 방식을 사용하여 값을 받아오는 방법이다.

useContext Hook 사용하기

리액트 내장함수 useContext라는 Hook을 사용하면 더 편리하고 코드가 간결해진다.
ColorBox.jsx

import React, { useContext } from 'react'
import ColorContext, { ColorConsumer } from '../contexts/color'

const ColorBox = () => {
  const { state } = useContext(ColorContext);
  return (
    <>
      <div
        style={{
          width: "64px",
          height: "64px",
          background: state.color,
        }}
      />
      <div
        style={{
          width: "32px",
          height: "32px",
          background: state.subColor,
        }}
      />
    </>
  );
}

export default ColorBox

SelectColors.jsx

import React, { useContext } from 'react'
import ColorContext, { ColorConsumer } from '../contexts/color';

const colors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet'];

const SelectColors = () => {
    const { actions } = useContext(ColorContext);
  return (
    <div>
      <h2>색상을 선택하세요. </h2>
      <div style={{ display: "flex" }}>
        {colors.map((color) => (
        <div
            key={color}
            style={{
            background: color,
            width: "24px",
            height: "24px",
            cursor: "pointer",
            }}
            onClick={() => actions.setColor(color)}
            onContextMenu={(e) => {
            e.preventDefault(); // 마우스 오른쪽 클릭시 메뉴가 뜨는것 무시
            actions.setSubColor(color);
            }}
        />
        ))}
    </div>
    </div>
  );
}

export default SelectColors

Hook은 함수형 컴포넌트에서만 사용가능하다.
클래스형 컴포넌트에서는 static contextType을 사용하면된다.

static contextType 사용하기

클래스형 컴포넌트에서는 static contextType을 사용하여 Context에 접근할 수 있다.

SelectColors_class.jsx

import React, { Component } from 'react'
import ColorContext from '../contexts/color';

const colors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet'];

class SelectColors extends Component {
  static contextType = ColorContext;

  handleSetColor = color => {
    this.context.actions.setColor(color);
  }
  handleSetSubColor = subColor => {
    this.context.actions.setSubColor(subColor);
  }

  render() {
    return (
      <div>
        <h2>색상을 선택하세요. </h2>
        <div style={{ display: "flex" }}>
          {colors.map((color) => (
            <div
              key={color}
              style={{
                background: color,
                width: "24px",
                height: "24px",
                cursor: "pointer",
              }}
              onClick={() => this.handleSetColor(color)}
              onContextMenu={(e) => {
                e.preventDefault(); // 마우스 오른쪽 클릭시 메뉴가 뜨는것 무시
                this.handleSetSubColor(color);
              }}
            />
          ))}
        </div>
      </div>
    );
  }
}

export default SelectColors

static contextType을 정의하면 클래스 메서드에서도 Context에 넣어둔 함수를 호출할 수 있다. 단점이라면, 한 클래스에서 하나의 Context밖에 사용하지 못한다는것. **
클래스형컴포넌트는 잘 사용하지 않기때문에 **useContext를 사용하는것을 권장
하고있다.

정리

기존에는 컴포넌트간에 상태를 교류해야할때 무조건 부모 -> 자식 흐름으로 props를 통해 전달해야했다. 이제 ContextAPI를 통해 더욱 쉽게 상태를 교류할 수 있게 되었다.
전역적으로 여기저기서 사용되는 상태가 있고 컴포넌트 갯수가 많은 상황이라면 ContextAPI를 사용하는것을 권한다.
하지만 단순한 전역상태관리라면 ContextAPI를 사용하면되지만, *리덕스의 더욱 향상된 성능과 미들웨어 기능, 강력한 개발자 도구, 코드의 높은 유지보수성 때문에 ContextAPI를 대체하지는 못한다. *

반응형

최근댓글

최근글

© Copyright 2023 jngmnj