본문 바로가기
JavaScript/React

React) Context API - 사용 이유, createContext, Consumer, Provider, 동적 Context 사용하기

by 박채니 2022. 11. 28.
안녕하세요, 코린이의 코딩 학습기 채니 입니다.
[리액트를 다루는 기술]의 책을 참고하여 포스팅한 개인 공부 내용입니다.

 

Context API

- 리액트 프로젝트에서 전역적으로 사용할 데이터가 있을 때 유용한 기능

- 리덕스, 리액트 라우터, styled-components 등의 라이브러리는 Context API를 기반으로 구현

 


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

리액트 애플리케이션은 컴포넌트 간의 데이터를 props로 전달하기 때문에 여러 컴포넌트에서 사용해야 하는 데이터들은 최상위 컴포넌트인 App에서 상태로 관리합니다.

 

props만을 이용한다면, 규모가 큰 리액트 애플리케이션을 구현할 때 유지 보수성이 낮아지기도 합니다.

Context API를 사용하지 않을 때

위와 같은 구조가 있을 때 Root에 있는 state를 G, F, J 컴포넌트가 가져오기 위해선 해당 구조를 거친 후 가져올 수 있는 것이죠.

이 때 해당 state가 필요 없는 컴포넌트들 또한 데이터를 갖게 될 것이고, 데이터가 변경되었을 때 부모 컴포넌트들 또한 수정이 필요할 것입니다.

 

이러한 문제점을 Context API로 해결 해줄 수 있습니다.

Context API를 사용할 때

부모 컴포넌트들을 거치지 않고도 Context를 통해 원하는 값을 한 번에 받아올 수 있습니다.

Context : 나 필요한 사람?

G, F, J 컴포넌트 : 나!!!

Context : 여기 데이터 줄게~


Context API 사용법 익히기

 

새 Context 만들기

 

src/contexts/color.js

import { createContext } from 'react';

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

export default ColorContext;

createContext 함수를 이용해 Context를 생성하고, 파라미터에는 해당 Context의 기본값을 지정해줍니다.

 

Consumer 사용하기

 

ColorBox 컴포넌트에서 ColorContext 안에 들어있는 색상을 props가 아닌 Consumer 컴포넌트를 통해 조회해보겠습니다.

 

src/components/ColorBox.js

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;

Consumer 사이에 중괄호를 열어 함수를 넣어주었습니다.

 

※ Render Props (Function as a child) ↓

더보기
  • 컴포넌트의 children이 있어야 할 자리에 일반 JSX 혹은 문자열이 아닌 함수 전달
const RenderPropsSample = ({ children }) => {
	return <div>결과 : {children(5)} </div>;
}

export default RenderPropsSample;

만일 위와 같은 컴포넌트가 있다면 아래와 같이 사용할 수 있습니다.

<RenderPropsSample>{value => 2 * value}</RenderPropsSample>;

결과 : 10을 리턴하게 되겠죠.

 

App.js

import ColorBox from './components/ColorBox';

const App = () => {
  return (
    <div>
      <ColorBox />
    </div>
  )
}

export default App;

 

Provider

 

Context의 value를 변경할 수 있습니다.

 

App.js

import ColorBox from './components/ColorBox';
import ColorContext from './contexts/color';

const App = () => {
  return (
    <ColorContext.Provider value={{color: 'red'}}>
        <div>
          <ColorBox />
        </div>
    </ColorContext.Provider>
  )
}

export default App;

위와 같이 빨간색 정사각형이 렌더링 되는 것을 확인할 수 있습니다.

 

createContext를 사용할 때 Context의 기본 값을 넣어주었는데, 해당 기본 값은 Provider를 사용하지 않을 때만 적용됩니다.

다만! 아래와 같이 Provider를 사용하고 value값을 지정해주지 않으면 오류가 발생합니다.

import ColorBox from './components/ColorBox';
import ColorContext from './contexts/color';

const App = () => {
  return (
    // 오류 발생 코드
    <ColorContext.Provider>
        <div>
          <ColorBox />
        </div>
    </ColorContext.Provider>
  )
}

export default App;

오류 발생

 


동적 Context 사용하기

 

Context 파일 수정하기

 

Context의 value에는 상태 값 뿐만 아니라 함수도 전달해줄 수 있습니다.

 

color.js

import { createContext, useState } 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;

ColorProvider 컴포넌트를 생성해주었고, 내부적으로 ColorContext.Provider를 렌더링하고 있습니다.

Provider의 value에는 상태는 state로, 업데이트 함수는 action으로 묶어 전달해주는 것을 확인할 수 있습니다.

 

또한, createContext의 기본값으로 사용할 객체를 Provider의 value에 넣는 객체의 형태와 일치시켜 주었는데요.

Context 코드를 볼 때 구성에 따른 파악이 쉽고, 실수로 Provider를 사용하지 않았을 때 에러 발생을 방지할 수 있습니다.

 

새로워진 Context를 프로젝트에 반영하기

 

App.js

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

const App = () => {
  return (
    <ColorProvider>
        <div>
          <ColorBox />
        </div>
    </ColorProvider>
  )
}

export default App;

 

ColorBox.js

import { ColorConsumer } from "../contexts/color";

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

export default ColorBox;

 

혹은

 

ColorBox.js - 비구조화 할당 문법 사용

import { ColorConsumer } from "../contexts/color";

const ColorBox = () => {
    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;

 

색상 선택 컴포넌트 만들기

 

components/SelectColor.js

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

const SelectColors = () => {
    return (
        <div>
            <h2>색상을 선택하세요.</h2>
            <div style={{display: 'flex'}}>
                {colors.map(color => (
                    <div 
                        key={color}
                        style={{
                            background: color,
                            width: '24px',
                            height: '24px',
                            cursor: 'poiner'
                        }}
                    />
                ))}
            </div>
            <hr />
        </div>
    )
}

export default SelectColors;

 

App.js

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

const App = () => {
  return (
    <ColorProvider>
        <div>
          <SelectColors />
          <ColorBox />
        </div>
    </ColorProvider>
  )
}

export default App;

 

해당 SelectColors에서 마우스 왼쪽 버튼 클릭 → 큰 정사각형의 색상 변경

마우스 오른쪽 버튼 클릭 → 작은 정사각형의 색상 변경을 해보도록 하겠습니다.

 

SelectColors.js

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: 'poiner' }}
                                onClick={() => actions.setColor(color)}
                                onContextMenu={e => {
                                    e.preventDefault(); //마우스 오른쪽 버튼 클릭 시 메뉴가 뜨는 것을 무시
                                    actions.setSubcolor(color);
                                }}
                            />
                        ))}
                    </div>
                )}
            </ColorConsumer>
            <hr />
        </div>
    )
}

export default SelectColors;

색상들이 잘 바뀌는 것을 확인할 수 있습니다.

왼쪽 마우스는 onClick 이벤트, 오른쪽 마우스는 onContextMenu 이벤트를 사용하여 처리하였습니다.

이 때 오른쪽 마우스 클릭 시 메뉴가 뜨는 것을 방지하기 위해 preventDefault 함수를 사용해주었습니다.