Context API

Posted by yunki kim on July 17, 2021

  Context API는 리액트 프로젝트에서 전역적으로 사용할 데이터가 있을 때 필요하다. Context API를 사용하지 않는다면 최상위 컴포넌트에서 다른 여러 컴포넌트에서 공통적으로 필요한 state를 관리하고 필요시 props의 형태로 전달한다. 이때 컴포넌트의 구조가 복잡해지면 여러 컴포넌트에 걸쳐서 props를 전달한다. 이는 유지보수성의 저하를 야기하기 때문에 이를 보완하기 위해 Context API 또는 Context API를 기반으로 구현된 리덕스, MobX같은 상태관리 라이브러리를 사용한다. 

  Context API를 사용하면 데이터의 전달을 위해 여러 컴포넌트를 거칠 필요 없이 Context를 만들어 한번에 원하는 값을 받아올 수 있다.

  /src/contexts에 다음과 같은 코드를 작성하자

1
2
3
4
5
//color.js
import
 {createContext} from 'react';
//context객체를 만든다
//context객체를 구독하고 있는 컴포넌트를 렌더링할 때 React는
//트리 상위에서 가장 가까이 있는 짝이 맞는 Provider로부터 현재
//값을 읽는다.
//아래 코드에서 createContext의 인자는 defaultValue로
//적절한 Provider를 찾지 못했을때 사용된다.
const ColorContext = createContext({color: black});
 
export default ColorContext;
cs

Consumer

  context 변화를 구독하는 React컴포넌트 이다. 이를 통해 함수형 컴포넌트에서 context를 구독할 수 있다.

  Context.Consumer의 자식은 반드시 함수여야 하며 이 함수는 context의 현재 값을 받고 React노드를 반환해야 한다. 이 함수가 받는 value 파라미터는 해당 context의 Provider 중 상위 트리에 가까운 Provider의 value prop과 동일하다. 상위 Provider가 없으면 createContext()의 defaultValue와 동일하다.

/src/components에 다음과 같은 코드를 작성하자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//ColorBox.js
import React from 'react';
import ColorContext from "../contexts/color";
 
const ColorBox = () => {
    return (
        //ColorContext안에 있는 Consumer 컴포넌트로 색을 받아온다
        <ColorContext.Consumer>
        //태그 사이에 {}를 넣고 그 안에 함수르 넣었다.
        //children대신 함수를 전달한다.
        //이것을 function as a child or Render Props라 한다.
            {value => (
                <div
                    style=
                />
            )}
        </ColorContext.Consumer>
    );
}
 
export default ColorBox;
cs

Render Props 예제:

1
2
3
4
5
6
7
8
import React from 'react';
const RenderPropsSample = ({children}) => {
    return <div>결과: {children(5)}</div>
};
export default RenderPropsSample;
 
//위와 같은 컴포넌트는 다음과 같이 사용하면 된다
<RenderPropsSample>{value => 2 * value}</RenderPropsSample>
cs

Provider

  Provider를 사용하면 Context의 value를 변경할수 있다. Provider는 context를 구독하는 컴포넌트들에게 context의 변화를 알리는 역할을 한다.

  Provider컴포넌트는 value prop를 받아서 하위 컴포넌트에 전달한다. Provider 컴포넌트의 하위 컴포넌트에 또 다른 Provider를 넣을 수 있다. 이 경우 가장 아래에 있는 Provider가 우선시 된다.

  Provider 하위에서 context를 구독하는 모든 컴포넌트는 Provider의 value prop가 바뀔때 마다 리렌더링 된다. Provider로 부터 하위 consumer로의 전파는 shouldComponentUpdate메서드가 적용되지 않기 때문에 상위 컴포넌트가 업데이트를 건너 뛰어도 consumer가 업데이트 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//App.js
//위 코드 중 color.js에서 createContext에 처음 설정한 값은
//Provider를 사용하지 않았을때 또는 value를 명시하지 않았을때만
//사용한다
import logo from './logo.svg';
import './App.css';
import ColorBox from "./components/ColorContext";
import ColorContext from "./contexts/color";
 
function App() {
  return (
      <ColorContext.Provider value=>
        <div>
          <ColorBox/>
        </div>
      </ColorContext.Provider>
  );
}
 
export default App;
 
cs

 

동적 Context 사용

  고정적인 값이 아닌 Context의 값을 업데이트 해야 하는 경우 다음과 같은 방식을 사용하면 된다. Context의 value에는 상태 뿐만 아니라 함수를 전달할 수 도 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
//context/color.js
import React, {createContext, useState} from 'react';
 
//createContext의 기본 값은 실제 Provider의 value에 넣는 객체의 형태와 일치시키는 것이 좋다
//일치시키면 Context를 볼떄 내부 값 파앆용이, Provider 오사용으로 안한 에러 발생 방지 효과 있음.
const ColorContext = createContext({
    state: {color: 'black', subcolor: 'red'},
    actions: {
        setColor: () => {},
        setSubColor: () => {},
    }
});
 
const ColorProvider = ({children}) => {
    const [color, setColor] = useState('black');
    const [subcolor, setSubcolor] = useState('red');
 
    //상태를 state로, setState는 actions로 묶어서 전달한다.
    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 logo from './logo.svg';
import './App.css';
import ColorBox from "./components/ColorContext";
import {ColorProvider} from "./contexts/color";
 
function App() {
  return (
      <ColorProvider>
        <div>
          <ColorBox/>
        </div>
      </ColorProvider>
  );
}
 
export default App;
 
 
//components/ColorContext.js
import React from 'react';
import {ColorConsumer} from "../contexts/color";
 
const ColorBox = () => {
    return (
        <ColorConsumer>
            {({state}) => (
                <>
                    <div
                        style=
                    />
                    <div
                        style=
                    />
                </>
            )}
        </ColorConsumer>
    );
}
 
export default ColorBox;
 
 
cs

Actions에 있는 SetState()호출

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
//components/SelectColors.js
import React from 'react';
import React from 'react';
import {ColorConsumer} from '../contexts/color';
 
const colors = ['red''orange''yellow''green''blue''indigo''violet'];
 
const SelectColors = () => {
    return (
        <div>
            <h2>Select color</h2>
            <ColorConsumer>
                {({actions}) => (
                    <div style=>
                        {colors.map(color => (
                            <div
                                key={color}
                                style=
                                onClick={() => actions.setColor(color)}
                                onContextMenu={e => {
                                     e.preventDefault();//마우스 오른쪽 버튼 클릭 시 메뉴가 뜨는 것을 무시함
                                    actions.setSubcolor(color);
                                }}
                            />
                        ))}
                    </div>
                )}
            </ColorConsumer>
            <hr/>
        </div>
    );
}
 
export default SelectColors;
 
//App.s
//...
import SelectColors from "./components/SelectColors";
 
function App() {
  return (
      <ColorProvider>
        <div>
            <SelectColors/>
            <ColorBox/>
        </div>
      </ColorProvider>
  );
}
 
export default App;
 
cs

클래스형 컴포넌트와 static contextType

 클래스형 컴포넌트에서 Context좀 더 쉽게 사용하고 싶다면 static contextType을 정의하면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
//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{
    //이러면 클래스 메서드에서도 Context에 넣어 둔 함수를 호출할 수 있다.
    //하지만 하나의 클래스에서 하나의 Context만 사용할 수 있따.
    static contextType = ColorContext;
 
    handleSetColor = color => {
        this.context.actions.setColor(color);
    };
 
    handleSetSubColor = subcolor => {
        this.context.actions.setSubcolor(subcolor);
    }
 
    render(){
        return (
            <div>
                <h2>Select color</h2>
                    <div style=>
                        {colors.map(color => (
                            <div
                                key={color}
                                style=
                                onClick={() => this.handleSetColor(color)}
                                onContextMenu={e => {
                                    e.preventDefault();//마우스 오른쪽 버튼 클릭 시 메뉴가 뜨는 것을 무시함
                                    this.handleSetSubColor(color);
                                }}
                            />
                        ))}
                    </div>
                <hr/>
            </div>
        );
    }
 
}
 
export default SelectColors;
cs