React를 다루는 기술 15 - Context API
Context API
Context API는 전역적인 상태관리를 할 때 유용한 라이브러리이다.
본래 리액트에서 해당 라이브러리를 사용하기 위해서는 다양한 사전작업이 필요했지만 v16.3부터 사용하기 쉬도록 개편되어 기본적인 전역적 상태관리를 할 때 많이 사용되고 있다.
이외에도 상태관리를 지원하는 라이브러리인 리덕스, 리액트 파우더, styled-components 등의 라이브러리들도 모두 Context API를 기반으로 구현되어있다.
전역 상태 관리
기본적으로 리액트는 자식 컴포넌트끼리의 호출이나 자식으로부터 props 없이 부모를 호출하는 것이 불가능하다.
보통은 부모 컴포넌트로부터 props를 통해 변수 혹은 함수를 받아 이를 통해 부모 혹은 다른 자식들을 호출하는 구조를 취하는데 때문에 바로 직계관계의 부모와 자식 컴포넌트가 아닌 다소 동떨어진 위치의 컴포넌트와 상태를 공유하기 위해서는 다소 복잡한 구조를 거칠 수 밖에 없다.
복잡한 구조는 즉 유지보수가 어렵다는 뜻이므로 이를 개선해야 할 필요가 있는데 여기서 사용되는 것이 전역 상태를 관리해주는 라이브러리 Context API이다.
새 Context 만들기
import { createContext } from 'react';
const ColorContext = createContext({ color: 'black' });
export default ColorContext;
새 Context를 만들 때는 리액트에 기본적으로 내장된 createContext 함수를 사용하여 초기상태를 지정해준다.
Consumer 사용하기
이제 ColorContext에서 설정한 값을 Consumer를 통해서 다른 컴포넌트에 가져와서 사용하는 코드를 작성하면 아래와 같다.
import React from 'react';
import ColorContext from './contexts/color';
const ColorBox = () => {
return (
<ColorContext.Consumer>
{value => (
<div
style=
/>
)}
</ColorContext.Consumer>
)
}
Context를 생성해준 ColorContext 컴포넌트 내부의 Consumer 컴포넌트를 넣고 그 사이에 중괄호를 열어 함수를 넣어서 오브젝트를 생성하였는데 이러한 패턴을 Function as a child 혹은 Render Props라고 한다.
Provider
Provider는 Context의 value를 변경하는 기능을 한다.
import React from 'react';
import ColorBox from './components/ColorBox';
import ColorContext from './contexts/color';
const App = () => {
return (
<ColorContext.Provider value=>
<div>
<ColorBox />
</div>
</ColorContext.Provider>
)
}
이렇게 Provider로 감싸주면 해당 컴포넌트의 자식 컴포넌트에 포함된 ColorContext 컴포넌트의 value값이 모두 바뀌게 된다. 기본적으로 Context생성시 만들어주었던 기본값은 Provider가 없는 경우에 한해서 사용되며 Provider를 통해 값을 변경해주었을 경우에는 해당 값이 우선된다.
동적 Context
동적 Context는 고정적인 값 만을 사용하는 것이 아니라 함수를 전달하는 것을 통해 동적으로 값을 받을 수 있는 것을 의미한다.
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;
export { ColorProvider, ColorConsumer };
export default ColorContext;
해당 Context는 Provider를 렌더링하고 있으며 Provider의 value에는 상태는 state로 업데이트는 action으로 묶어서 전달하고 있다.
이렇게 state, action을 따로따로 분리해서 보내는 것은 필수적인 사항은 아니지만 이 후 다른 컴포넌트에서 사용할 때 편하기 때문에 권장되는 사항이다.
Context의 초기값도 Provider와 동일한 형태로 변경되었는데 이는 기본적인 구성파악의 용이함과 Provider를 빼먹었을 때 에러가 발생하는 것을 방지하기 위함이다.
Consumer 대신 다른 것들 사용하기
- useContext Hook 사용하기
useContext는 리액트에 내장된 Hook 중 하나로 이를 통해 함수형 컴포넌트에서 쉽게 Context를 사용할 수 있다.
import React from 'react';
import ColorContext from './contexts/color';
const ColorBox = () => {
const { state } = useContext(ColorContext);
return (
<>
<div
style=
/>
<div
style=
/>
</>
)
}
- static contextType 사용하기
클래스형 컴포넌트에서 Context를 좀 더 쉽게 사용하고 싶다면 static contextType를 사용하면 된다.
import React, { Component } from 'react';
import ColorContext from '../contexts/color';
const colors = ['red', 'orange', 'yellow'];
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=>
{colors.map(color => {
<div
key={color}
style=
onClick={() => this.handleSetColor(color)}
onContextMenu={e => {
e.preventDefault();
this.handleSetSubcolor(color);
}}
/>
})}
</div>
</div>
)
}
}
이렇게 Context를 static변수에 담은 뒤 렌더링되는 오브젝트와 함수를 통해 연결해두면 Class 컴포넌트에서도 Context를 쉽게 사용할 수 있다.
다만 한 가지 단점은 이 방법을 사용할 경우 하나의 클래스에서 하나의 Context만이 사용가능하다는 점이다.
출처자료
https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=209354690 (리액트를 다루는 기술(개정판))