본문 바로가기
Frontend/ReactJS(완)

React - 15장 : ContextAPI

by 리키이 2023. 2. 23.

<리액트를 다루는 기술>

React - 1장 : React 이해

React - 2장 : JSX

React - 3장 : 컴포넌트

React - 4장 : 이벤트 핸들링

React - 5장 : ref. DOM에 이름 달기 

React - 6장 : 컴포넌트 반복
React - 7장 : 컴포넌트의 LifeCycle
React - 8장 : React Hooks 총정리

React - 9장 : 컴포넌트의 스타일링

React - 10장 : 빠르게 TODO앱 실습

React - 11장 : 컴포넌트 성능 최적화

React - 12장 :  immer를 사용하여 더 쉽게 불변성 유지하기

React - 13장 : 리액트 라우터로 SPA 개발하기

React - 14장 : 뉴스 뷰어 예제프로젝트

React - 15장 : ContextAPI(현재)

 


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

- 리액트 애플리케이션은 컴포넌트 간에 데이터를 props로 전달하기 때문에 컴포넌트 여기저기서 필요한 데이터가 있을 때는 주로 최상위 컴포넌트인 App의 state에 넣어서 관리한다.

- 기존에는 최상위 컴포넌트에서 여러 컴포넌트를 거쳐 props로 원하는 상태와 함수를 전달했지만, Context API를 사용하면 Context를 만들어 단 한 번에 원하는 값을 받아 와서 사용할 수 있다.

 

 

Context API

 

15-2. 실습

 

//프로젝트 하나 새로 생성

yarn create react-app context-tutorial

 

- contexts 디렉터리를 만든 뒤 그 안에 color.js라는 파일을 만들어서 다음과 같이 작성

 

import { createContext } from 'react';

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

export default ColorContext;

 

- ColorBox라는 컴포넌트를 만들어서 ColorContext 안에 들어 있는 색상을 보여 주려고 한다.

- 색상을 props로 받아 오는 것이 아니라 ColorContext 안에 들어 있는 Consumer라는 컴포넌트를 통해

  색상을 조회해보자

- Consumer 사이에 중괄호를 열어서 그 안에 함수를 넣어 주었는데 이러한 패턴을

  Function as a child, 혹은 Render Props라고 한다.

 

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.js로 돌아와서 랜더링

 

import React from 'react';
import ColorBox from './components/ColorBox';
const App = () => {
  return (
    <div>
      <ColorBox />
    </div>
  );
};
 
export default App;

 

- 결과를 확인해보면 색상 'black' 이 잘 나오는 것을 볼 수 있다.

 

1) Provider

- Provider를 사용하면 Context의 value를 변경할 수 있다. App.js를 수정해보자

- Provider는 사용할 때에는 value를 명시하지 않으면 오류가 발생한다.

 

import React from 'react';
import ColorBox from './components/ColorBox';
import ColorContext from './contexts/color';
const App = () => {
  return (
  // Provider를 사용할 때는 value값 모조건 넣기
    <ColorContext.Provider value={{ color: 'red' }}>
      <div>
        <ColorBox />
      </div>
    </ColorContext.Provider>
  );
};
 
export default App;

 

2)  동적 Context 사용하기

 

- Context의 value에는 무조건 상태 값만 있어야 하는 것은 아니라 함수를 전달해 줄 수도 있다.

- color.js를 다음과 같이 수정해보자

- 위 파일에서 ColorProvider라는 컴포넌트를 새로 작성해 주었습니다. 그리고 그 컴포넌트에서는 ColorContext.Provider를 렌더링하고 있죠. 

추가로 createContext를 사용할 때 기본값으로 사용할 객체도 수정했습니다. createContext의 기본값은 실제 Provider의 value에 넣는 객체의 형태와 일치시켜 주는 것이 좋습니다. 그렇게 하면 Context 코드를 볼 때 내부 값이 어떻게 구성되어 있는지 파악하기도 쉽고, 실수로 Provider를 사용하지 않았을 때 리액트 애플리케이션에서 에러가 발생하지 않습니다.

 

import React, { createContext, useState } from 'react';
 
const ColorContext = createContext({
  state: { color: 'black', subcolor: 'red' },
  actions: {
    setColor: () => {},
    setSubcolor: () => {}
  }
});
 
 // Provider의 value에는 상태는 state로, 업데이트 함수는 actions로 묶어서 전달한다. 
 // Context에서 값을 동적으로 사용할 때 반드시 묶어 줄 필요는 없지만, 
 // 이렇게 state와 actions 객체를 따로따로 분리해 주면 
 // 나중에 다른 컴포넌트에서 Context의 값을 사용할 때 편하다.
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;

 

- App.js 다음과 같이 수정

import React from 'react';
import ColorBox from './components/ColorBox';
import { ColorProvider } from './contexts/color';
const App = () => {
  return (
    <ColorProvider>
      <div>
        <ColorBox />
      </div>
    </ColorProvider>
  );
};
 
export default App;

 

- ColorBox.js 도 다음과 같이 수정

import React from 'react';
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;

 

3) 색상 선택 컴포넌트 만들기

- 이제 해당 SelectColors에서 마우스 왼쪽 버튼을 클릭하면 큰 정사각형의 색상을 변경하고,

  마우스 오른쪽 버튼을 클릭하면 작은 정사각형의 색상을 변경하도록 구현해보자.

- components 디렉터리에 SelectColors.js라는 파일을 생성하고 다음과 같이 작성

 

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를 사용하면 된다.
                onContextMenu={e => {
                  e.preventDefault(); // 마우스 오른쪽 버튼 클릭 시 메뉴가 뜨는 것을 무시함
                  actions.setSubcolor(color);
                }}
              />
            ))}
          </div>
        )}
      </ColorConsumer>
      <hr />
    </div>
  );
};
 
export default SelectColors;

 

- App 컴포넌트에서 ColorBox 위에 SelectColors.js 렌더링

 

import React from 'react';
import ColorBox from './components/ColorBox';
import { ColorProvider } from './contexts/color';
import SelectColors from './components/SelectColors';
 
const App = () => {
  return (
    <ColorProvider>
      <div>
        <SelectColors />
        <ColorBox />
      </div>
    </ColorProvider>
  );
};
 
export default App;

 

15-3. Consumer 대신 Hook 또는 static contextType 사용하기

 

1) useContext Hook 사용하기

- 리액트에 내장되어 있는 Hooks 중에서 useContext라는 Hook을 사용하면, 함수형 컴포넌트에서 Context를 편하게

   사용할 수 있다.

- children에 함수를 전달하는 Render Props 패턴이 불편하다면, useContext Hook을 사용하여 훨씬 편하게

  Context 값을 조회할 수 있다.

- Hook은 함수형 컴포넌트에서만 사용할 수 있다

- ColorBox.js 다음과 같이 수정

 

<consumer를 쓸때>

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


<useContext를 쓸때>
import React, { useContext } from 'react';
import ColorContext 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;

 

 

- 결과적으로 useContext Hook을 사용하여 SelectColors.js와 color.js을 깜끌하게 코드정리를 해보면 

  다음과 같다.

 

<SelectColors.js>

import React, { useContext } from 'react';
import ColorContext 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를 사용하면 된다.
                        onContextMenu={(e) => {
                            e.preventDefault(); // 마우스 오른쪽 버튼 클릭 시 메뉴가 뜨는 것을 무시함
                            actions.setSubcolor(color);
                        }}
                    />
                ))}
            </div>
            <hr />
        </div>
    );
};

export default SelectColors;

<color.js>

import React, { createContext, useState } from 'react';

const ColorContext = createContext({
    state: { color: 'black', subcolor: 'red' },
});

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>;
};

export { ColorProvider };

export default ColorContext;

댓글