프로젝트 내에서 환경 설정, 사용자 정보와 같은 전역적으로 필요한 상태를 관리해야 할 때 사용한다.
리액트 애플리케이션은 컴포넌트 간에 데이터를 props로 전달하기 때문에 여기저기서 필요한 데이터가 있을 때는 주로 최상위 컴포넌트인 App의 state에 넣어서 관리한다.
이러한 상황에서는 컴포넌트 목적지까지 많은 컴포넌트를 거쳐야 할 때도 있고 다루어야하는 데이터가 훨씬 많아질수도 있기 때문에 유지보수성이 낮아질 가능성이 있다.
리덕스나 MobX와 같은 상태 관리 라이브러리를 사용하여 전역 상태 관리 작업을 더 편하게 처리하기도 한다.
리액트 v16.3 업데이트 이후에는 Context API가 많이 개선되었기 때문에 라이브러리를 사용하지 않아도 전역 상태를 손쉽게 관리할 수 있다.
Context API를 사용하면 Context를 만들어 단 한 번에 원하는 값을 받아 와서 사용할 수 있다.
contexts/color.js
import { createContext } from "react";
const ColorContext = createContext({ color: "black" });
export default ColorContext;
components/ColorBox.js
import ColorContext from "../contexts/color";
// ColorContext 안에 들어있는 Consumer라는 컴포넌트를 통해 색상을 조회한다.
const ColorBox = () => {
return (
<ColorContext.Consumer>
{(value) => (
<div
style={{
width: "64px",
height: "64px",
background: value.color,
}}
></div>
)}
</ColorContext.Consumer>
);
};
export default ColorBox;
컴포넌트를 만들어서 Context안에 있는 색상을 가져올 수 있다.
Consumer 사이에 중괄호를 넣어서 그 안에 함수를 넣어준다.
이러한 패턴을 function as a child 혹은 Render Props라고 한다.
컴포넌트의 children이 있어야 할 자리에 일반 JSX 혹은 문자열이 아닌 함수를 전달하는 것이다.
Provider를 사용하면 Context의 value를 변경할 수 있다.
App.js
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;
Context에서 지정한 black 색상이 Provider를 통해서 red로 변경되었다.
createContext 함수에서 사용하는 파라미터('black')은 기본값이다.
Provider를 사용하지 않았을 때만 사용된다.
Provider를 사용할 때는 value 값을 명시해 주어야 제대로 작동한다.
contexts/color.js
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 }, // 업데이트 함수
};
// Context에서 값을 동적으로 사용할 때 반드시 묶어 줄 필요는 없지만,
// state와 actions 객체를 따로 분리해주면 나중에 다른 컴포넌트에서 Context 값을 사용할 때 편리하다.
return <ColorContext.Provider value={value}>{children}</ColorContext.Provider>;
};
// const ColorConsumer = ColorContext.Consumer와 같은 의미
const { Consumer: ColorConsumer } = ColorContext;
// ColorProvider와 ColorConsumer 내보내기
export { ColorProvider, ColorConsumer };
export default ColorContext;
ColorProvider라는 컴포넌트를 새로 작성했다.
ColorContext.Provider를 렌더링하고 있다.
이 Provider의 value에서 상태는 state로 업데이트 함수는 actions로 묶어서 전달하고 있다.
App.js
import ColorBox from "./components/ColorBox";
import SelectColors from "./components/SelectColors";
import { ColorProvider } from "./contexts/color";
const App = () => {
return (
<ColorProvider>
<div>
<SelectColors />
<ColorBox />
</div>
</ColorProvider>
);
};
export default App;
components/ColorBox.js
import ColorConsumer from "../contexts/color";
// ColorContext 안에 들어있는 Consumer라는 컴포넌트를 통해 색상을 조회한다.
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;
components/SelectColors.js
import { ColorConsumer, 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>
</div>
);
};
export default SelectColors;
components/ColorBox.js
import { useContext } from "react";
import ColorContext from "../contexts/color";
// ColorContext 안에 들어있는 Consumer라는 컴포넌트를 통해 색상을 조회한다.
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 Hook을 사용하여 훨씬 편하게 Context 값을 조회할 수 있다.
Hook은 함수 컴포넌트에서만 사용할 수 있다는 것을 주의하자.