본문 바로가기

Front/React

[React] Context API

  • 전역적으로 사용할 데이터가 있을 때 유용한 기능
    • ex) 사용자 로그인 정보, 애플리케이션 환경 설정, 테마 등 
  • 리액트 관련 라이브러리에서도 많이 사용
    • ex) 리덕스, 리액트 라우터, styled-components등

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

  • 컴포넌트 간에 데이터를 props로 전달하기 때문에 여기저기서 필요한 데이터가 있을 때는 주로 최상위 컴포넌트인 App의 state에 넣어 관리
  • 기존엔 최상위 컴포넌트에서 여러 컴포넌트를 거쳐 props로 원하는 상태와 함수를 전달했지만, Context API를 사용하면 Context를 만들어 단 한 번에 원하는 값을 받아 와서 사용할 수 있다.

 

2. Context API 사용법 익히기

2.1. 새 Context 만들기

[contexts/color.js]

import {createContext} from 'react';

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

export default ColorContext

 

2.2. Consumer 사용하기

  • ColorContext안에 들어있는 색상을 보여줄 것이다.
  • 색상을 props로 받아 오는 것이 아니라 ColorContext 안에 들어 있는 Consumer 라는 컴포넌트를 통해 조회할 것

[components/ColorBox.js]

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
  • Consumer 사이에 중괄호를 열어서 그 안에 함수를 넣어줬다.
    • 이러한 패턴을 Function as a child, 혹은 Render Props라고 한다.
    • 컴포넌트의 child가 있어야 할 자리에 일반 JSX 혹은 문자열이 아닌 함수를 전달 하는 것

 

[App.js]

import React from 'react';
import ColorBox from './components/ColorBox';

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

export default App;

 

[결과]

2.3. Provider

  • Context의 value를 변경할 수 있다.

[App.js]

import React from 'react';
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;
  • 만약 Provider는 사용했는데 value를 명시하지 않았다면, 오류발생

[결과]

 

3. 동적 Context 사용하기

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

 

[contents/color.js]

import React, {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
  • Provider의 value 상태는 state로, 업데이트 함수는 actions로 묶어서 전달하고 있다.
    • 따로 분리해주면 나중에 다른 컴포넌트에서 Context의 값을 사용할 때 편하다.
  • createContext의 기본값을 실제 Provider의 value에 넣는 객체의 형태와 일치시켜 주는 것이 좋다.
    • 내부 값이 어떻게 구성되어 있는지 파악하기 쉽고, 실수로 Provider를 사용하지 않았을 때 에러발생하지 않음.

 

[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;
  • ColorContext.Provider를 ColorProvider로 대체

 

[components/ColorBox.js]

import React from 'react';
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
  • ColorContext.Consumer 를 ColorConsumer로 변경
  • 위 코드에서 객체 비구조화 할당 문법을 사용하면 다음과 같이 value를 조회하는 것을 생략할 수 있다.

[components/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.3.색상 선택 컴포넌트 만들기

  • Context의 actions에 넣어 준 함수를 호출하는 컴포넌트를 만들어 보겠다.

[components/SelectColors.js]

import React from 'react';

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: 'pointer'
                        }}
                    />
                ))}
            </div>
            <hr />
        </div>
    );
};

export default SelectColors;

 

[App.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;

 

[결과]

 

  • 이제 마우스 왼쪽 버튼을 클릭하면 큰 정사각형의 색상을 변경하고, 마우스 오른쪽 버튼을 클릭하면 작은 정사각형의 색상을 변경하도록 구현해 보겠다.

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

export default SelectColors;

 

 

[결과]

 

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

4.1. useContext Hook 사용하기

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

[components/ColorBox.js]

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

 

  • 만약 children에 함수를 전달하는 Render Props패턴이 불편하다면, useContext Hooks을 사용하여 훨씬 편하게 Context 값을 조회할 수 있다.
  • 그러나 Hook은 함수형 컴포넌트에서만 사용할 수 있다.

 

 

4.2. static contextType 사용하기

  • 클래스형 컴포넌트에서 Context를 좀 더 쉽게 사용하고 싶을 때 사용

[components/SelectColors.js]

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>
                <hr />
            </div>
        );
    }
    
};

export default SelectColors;