애플리케이션을 만들다 보면, 로그인 정보, 환경 설정 등 전역적으로 사용해야 하는 정보 등이 있을 것이다.
지금까지 배운 지식으로는 여러 컴포넌트 간에 사용하는 데이터가 있을 경우, 상위 컴포넌트에서부터 props 형태로 건네주는 방식으로 공유를 하고 있다. 하지만 프로젝트의 규모가 커지고, 컴포넌트가 겹겹이 싸여있는 경우 여러 컴포넌트를 거쳐서 내려가야 하므로 유지 보수성이 낮아질 가능성이 있다.
그렇기 때문에 리덕스나 MoBX 같은 상태 관리 라이브러리를 이용해 전역 상태 관리 작업을 더 편하게 처리하기도 하는데, 리액트 v16.3 업데이트 이후에는 Context API가 많이 개선되어서 별도의 라이브러리 없이도 전역 상태를 손쉽게 관리할 수 있다.
새 context를 만들 때는 createContext
함수를 사용한다. 파라미터에는 해당 context의 기본 상태를 지정한다.
import { createContext } from "react";
const ColorContext = createContext({color: 'black'});
export default ColorContext;
import React from "react";
import ColorContext from "../contexts/color";
const ColorBox = () => {
return (
<ColorContext.Consumer>
{(value) => (
<div
style={{
width: "64px",
height: "64px",
background: value.color,
}}
/>
)}
</ColorContext.Consumer>
);
};
export default ColorBox;
사용할 context의 이름 뒤에 .Consumer
을 붙인 컴포넌트 안에 중괄호를 열어서 함수를 넣어 주었다.
이러한 패턴을 Function as a child
혹은 Render props
라고 한다. 컴포넌트의 children 자리에 JSX, 문자열이 아닌 함수를 전달하는 것이다.
context 값은 함수의 파라미터로 (위 코드에서는 value
)로 사용할 수 있다.
위의 컴포넌트를 렌더링 해 보면 검은색 정사각형이 나타나는 것을 확인할 수 있다.
Provider는 context의 값을 변경할 수 있다. 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의 이름 뒤에 .Provider
를 붙인 컴포넌트로 감싸준다.
그리고 value
속성을 수정해 줄 수 있다.
그 결과, 빨간색 정사각형이 나타나는 것을 볼 수 있다.
Context의 value
에는 무조건 상태 값만 있어야 하는 것은 아니고 함수를 전달해 줄 수도 있다.
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;
export { ColorProvider, ColorConsumer };
export default ColorContext;
위에서 ColorProvider
라는 컴포넌트를 새로 작성해 주었고 그 컴포넌트에서는 ColorContext.Provider
을 렌더링하고 있다. 그리고 이 Provider의 value는 상태는 state로, 업데이트 함수는 actions로 묶어서 전달하고 있다.
createContext
를 사용할 때 기본값으로 사용할 객체도 수정했다.
createContext
의 기본값은 Provider의 value
에 넣는 객체의 형태와 일치시켜주는 것이 좋다.
이제 App.js
의 ColorContext.Provider
를 ColorProvider
로 교체한다.
import React from "react";
import ColorBox from "./components/ColorBox";
import { ColorProvider } from "./contexts/color";
const App = () => {
return (
<ColorProvider>
<div>
<ColorBox />
</div>
</ColorProvider>
);
};
export default App;
마찬가지로 ColorBox.js
에서 ColorContext.Consumer
역시 ColorConsumer
로 교체하고, 밑에 작은 사각형을 하나 더 만든다.
import React from "react";
import {ColorConsumer} from '../contexts/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;
두 개의 정사각형이 렌더링되었다.
이제 Context의 actions
함수를 호출하는 컴포넌트를 만들어 본다.
SelectColor.js
import React from "react";
const colors = ["red", "orange", "yellow", "green", "blue", "indigo", "violet"];
const SelectColor = () => {
return (
<div>
<h2>색상을 선택하세요.</h2>
<div style={{ display: "flex" }}>
{colors.map((color) => (
<div
key={color}
style={{
background: color,
width: "24px",
height: "24px",
cursor: "pointer",
}}
/>
))}
</div>
<hr />
</div>
);
};
export default SelectColor;
일곱 개의 색깔을 배열로 만들었고 map
함수를 이용해 하나씩 24px 크기의 정사각형을 만들었다. 그리고 이를 ColorBox
위에 렌더링해주면
위와 같은 정사각형들이 나타난다.
이제 SelectColor
에서 마우스 왼쪽 버튼을 클릭하면 큰 정사각형의 색깔을, 마우스 오른쪽 버튼을 클릭하면 작은 정사각형의 색상을 변경하도록 구현해보자.
SelctColor.js
의 ColorConsumer
내부의 코드를 다음과 같이 변경한다.
<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>
마우스 좌클릭은 onClick
, 우클릭은 onContextMenu
이벤트로 설정할 수 있다. 이 때 우클릭을 할 경우 메뉴 창이 뜨므로 preventDefault()
을 통해 기본 기능을 꺼준다.
그러면 클릭함에 따라 색깔이 잘 바뀌는 것을 확인할 수 있다.
리액트에 내장되어 있는 useContext
라는 훅을 사용하면 함수형 컴포넌트에서 Context를 아주 편하게 사용할 수 있다.
ColorBox.js
을 다음과 같이 수정한다.
import React, { useContext } from "react";
import ColorContext from "../contexts/color";
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;
우선 ColorConsumer
를 사용하지 않아도 되고, value
인자 없이 바로 state
에서 꺼내어 사용할 수 있다.
기존에는 컴포넌트 간에 state를 공유해야 할 때 무조건 부모 -> 자식 순서로 props를 통해 건네주었다면, 이제는 Context API를 통해 쉽게 상태를 공유할 수 있게 되었다.