Context API는 전역적으로 사용할 데이터가 있을 떄 유용한 기능이다
ex) 사용자 로그인 정보, 애플리케이션 환경 설정, 테마 등
리액트 애플리케이션은 컴포넌트 간 데이터를 props
로 전달하기 때문에 주로 최상위 컴포넌트인 APP의 state
에 넣어 관리한다
이렇게 되면 컴포넌트가 많아지고 복잡해질 경우 유지 보수성이 낮아진다는 문제가 발생한다
문제를 해결하기 위해 Context AP 같은 상태 관리 라이브러리를 사용한다
const { createContext } = require("react");
const ColorContext = createContext({ color: "black" });
export default ColorContext;
새 Context를 만들 때는 createContext
함수를 사용하고 파라미처에는 해당 Context의 기본 상태를 지정한다
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 혹은 문자열이 아닌 함수를 전달하는 것
const RenderPropsSample = ({ children }) => {
return <div>결과: {children(5)}</div>;
};
export default RenderPropsSample;
사용법은 다음과 같다
<RenderPropsSample>{value => 2 * value}</RenderPropsSample>
출력 결과는 다음과 같다
결과: 10
Provider를 사용하면 Context의 value를 변경할 수 있다
<ColorContext.Provider value={{ color: "red" }}>
<div>
<ColorBox />
</div>
</ColorContext.Provider>
createContex 함수의 파라미터로 넣어준 Context의 기본값은 Provider를 사용하지 않았을 때만 사용된다
Provider는 사용했는데 value를 명시하지 않았다면 오류가 발생한다
Context의 value에는 상태 값 말고도 함수를 전달해 줄 수도 있다
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 { Consumer: ColorConsumer } = ColorContext;
export { ColorProvider, ColorConsumer };
export default ColorContext;
Provider의 value처럼 state와 actions 객체를 따로따로 분리해 주면 나중에 다른 컴포넌트에서 Context의 값을 사용할 때 편하다 (반드시 묶을 필요는 없다)
createContext의 기본값은 실제 Provider의 value에 넣는 객체의 형태와 일치시켜 주는 것이 좋다
ColorContext.Provider를 ColorProvider로 변경하자
<ColorProvider>
<div>
<ColorBox />
</div>
</ColorProvider>
ColorContext.Consumer도 마찬가지로 ColorConsumer로 변경하자
<ColorConsumer>
{({ state }) => (
<>
<div
style={{
width: "64px",
height: "64px",
background: state.color,
}}
/>
<div
style={{
width: "32px",
height: "32px",
background: state.subcolor,
}}
/>
</>
)}
</ColorConsumer>
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>
);
};
마우스 오른쪽 버튼 클릭 이벤트는 onContextMenu
를 사용하면 된다
원래는 브라우저 메뉴가 나타나지만 e.preventDefault()
를 호출하면 뜨지 않는다
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,
}}
/>
</>
);
};
Render Props 패턴을 사용할 때보다 훨씬 간결해진 것을 볼 수 있다
static contextType을 정의하는 방법은 클래스형 컴포넌트에서 사용하는 방식이다
클래스 상단에 static contextType = ColorContext;
처럼 static contextType 값을 지정하면 this.context
를 조회했을 때 현재 Context의 value를 가리키게 된다