Context API는 리액트 프로젝트에서 전역적으로 사용할 데이터(로그인 정보, 환경 설정, 테마 등)
가 있을 때 유용하게 사용된다.
리덕스, 리액트 라우터, styled-components 등의 라이브러리들은 Context API를 기반으로 구현된다.
1. Context API를 사용한 전역 상태 관리 흐름
리액트 애플리케이션은 컴포넌트간 데이터를 props로 전달하기에 필요한 데이터가 있을 때
주로 최상위 컴포넌트인 App의 state에 넣어 관리한다.
→ 항상 최상위 컴포넌트에서 목표하는 컴포넌트에 정보를 전달하기 까지
연결된 컴포넌트를 여러번 거쳐야한다.
이런 방식은 유지 보수성이 낮아질 가능성이 있다.
이러한 문제를 해결하려면 Context API를 사용하면 된다.
Context API를 사용하면 Context를 만들어 단 한 번에 원하는 값을 받아와서 사용할 수 있다.
2. Context API
Context API는 react에 내장된 API이다.
import { createContext, useState } from 'react';
const ColorContext = createContext({ color: 'black' });
export default ColorContext;
새로운 context를 만들때는 createContext 함수를 사용한다.
파라미터에는 해당 Context의 기본 상태를 저장한다.
- Consumer 사용
Consumer 특징
- context의 변화를 구독한다.
- 자식은 함수여야 하고 context의 현재 값을 받고 react노드를 반환한다.
- context 에서 가장 가까운 Provider value를 참조한다.
import ColorContext from '../context/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라고 한다.
컴포넌트의 children이 있어야 할 자리에 일반 JSX 혹은 문자열이 아닌 함수를 전달
- Provider 사용
- Provider를 사용하여 Context의 value를 변경할 수 있다.
- Context에 전달한 기본값은 Provider를 사용하지 않았을 때 적용된다.
- value를 명시하지 않고 Provider를 사용하면 에러가 발생한다.(기본값 사용X)
import ColorBox from './component/ColorBox';
import ColorContext from './context/color';
const App = () => {
return (
<ColorContext.Provider value={{ color: 'red' }}>
<div>
<ColorBox />
</div>
</ColorContext.Provider>
)
}
export default App;
3. 동적 Context 사용하기
지금부터 Context의 값을 업데이트해야 하는 경우를 알아보자
- Context 파일 수정
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;
//const ColorProvider와 ColorConsumer 내보내기
export { ColorProvider, ColorConsumer };
export default ColorContext;
ColorContext.Provider를 렌더링한다. 이 Provider의 value에는 상태를 state,
업데이트 함수는 action으로 묶어서 전달한다.
(state와 actions 객체를 분리하면 다른 컴포넌트에서 Context 값을 사용하기 편하다)
또한, createContext의 기본값은 실제 Provider의 value에 넣는 객체의 형태와 일치시켜 주는 것이 좋다.
- 새로워진 Context를 프로젝트에 반영하기
// App.js
import ColorBox from './component/ColorBox';
import { ColorProvider } from './context/color';
const App = () => {
return (
<ColorProvider>
<div>
<ColorBox />
</div>
</ColorProvider>
)
}
export default App;
사용할 value의 형태도 바뀌었으니 변화를 반연시켜보자
import { ColorConsumer } from '../context/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;
위의 코드를 객체 비구조화 할당 문법을 사용해 작성해보자. value를 조회하는 것을 생략할 수 있다.
import { ColorConsumer } from '../context/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;
- 색상 선택 컴포넌트 만들기
import { ColorConsumer } from '../context/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;
마우스 우클릭 이벤트는 onContextMenu를 사용하면 되고,
브라우저 메뉴가 나타나는 것을 방지하려면 e.preventDefault()를 호출하면 된다.
4. Hook 또는 static contextType 사용
- useContext Hook 사용하기
리액트에 내장된 Hooks인 useContext를 사용하면, 함수 컴포넌트에서 Context를 쉽게 사용할 수 있다.
import { useContext } from 'react';
import ColorContext from '../context/color';
const ColorBox2 = () => {
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 ColorBox2
(주의점!! 클래스형 컴폰너트에서는 Hooks를 사용할 수 없으니 조심하자)
- static contextType 사용하기
import { Component } from 'react';
import ColorContext from '../context/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.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;
static contextType을 정의하면 클래스 메서드에서도 context에 넣어 둔 함수를 호출할 수 있는 장점이 있다.
단점은 하나의 Context밖에 사용하지 못하는 것이다.
💓앞으로는 클래스형으로 작성하는 일이 많지 않기에 useContext의 사용을 권장한다.
5. 정리
전역적으로 여기저기서 사용되는 상태가 있고 컴포넌트의 개수가 많다면 Context API의 사용을 권장한다.
'React' 카테고리의 다른 글
React) 17장 리덕스를 이용한 Todo리스트 (0) | 2022.05.14 |
---|---|
React) 16장 redux 라이브러리 (0) | 2022.05.12 |
React) 14장 외부 API를 이용한 뉴스 뷰어 (0) | 2022.05.10 |
React) 13장 리액트 라우터로 SPA개발하기 (0) | 2022.05.07 |
React) 12장 immer 라이브러리 (0) | 2022.05.06 |