ContextAPI란?
리액트에서 컴포넌트간 데이터의 흐름은 단방향 입니다. 이러한 특징은 JavaScript라는 언어의 특성 상 디버깅이 어렵다는 단점을 보완하기 위해 도입된 rule에서 기인하는데요. 그래서 컴포넌트 간에 데이터를 전달하려면, 부모 컴포넌트로부터 props의 형태로 값을 물려 받아야 할 것입니다. 개발을 편하게 하려고 이러한 규칙을 정했다지만, 개발을 하다보면 컴포넌트 간에 데이터를 송수신 해야 할 경우가 무척이나 많겠죠? 아래의 모식도를 보겠습니다.
리액트로 개발된 어떤 SPA가 위의 모식도와 같이 구성되어 있다고 가정해 봅시다. 개발자가 C라는 컴포넌트에서 이벤트가 발생하면 이웃한 B와 E 컴포넌트에서 어떤 데이터가 반영되고자 한다면 모식도 처럼 최상위 컴포넌트인 App 컴포넌트부터 시작하여 B를 업데이트 하기 위해 App → A → B 순서로 데이터가 이동하고, E를 업데이트 하기 위해 App → D → E 로 데이터를 이동하게 해야 합니다. 리액트의 컴포넌트는 props가 변경되면 리렌더링 이 발생하기 때문에 B와 E만 바꾸고 싶었어도 결과적으로는 A와 D까지 리렌더링이 되게 됩니다. 또한, 이러한 플로우는 프로그램 규모가 커지면 서비스 성능에도 영향을 줄 수 있기 때문에 프로그래밍 언어 Java나 C처럼 일종의 전역변수 처럼 사용할 수 있는 것에 대한 요구가 생기게 되었고, 이러한 요구를 해결하기 위한 방법 중 하나가 바로 Context API 입니다. 다음 챕터에서는 조금 더 고도화된 Redux라는 것을 통해 이를 관리하게 될 테인데요. Redux도 좋지만 Context API도 이러한 데이터를 송수신 하는 데 사용하기 나쁘지 않아서 필요하다면 개발자의 재량으로 얼마든지 활용할 수 있습니다.
Context
Context API를 사용하기 위해서는 데이터를 담고 송수신 할 일종의 허브(Hub)가 필요한데요. 이때 사용되는 것이 Context 입니다. 개발자가 서로 식별하기 쉽게 이러한 이름으로 호칭 하는 것이며 Context가 될 컴포넌트의 실제 이름은 무엇이든 상관 없습니다.
color.js
import { createContext } from "react";
const ColorContext = createContext({ color: "black" });
export default ColorContext;
- 색상에 대한 CSS 속성 값을 담는 Context 인 Color.js 를 위와 같이 선언합니다.
Context Consumer
- Context로부터 값을 받아서 컴포넌트에 적용하려면 Context Consumer를 통해 값을 받아와야 합니다.
- Context Consumer는 아래와 같이 사용할 수 있습니다.
ColorBox.js
import ColorContext from "../contexts/color";
const ColorBox = () => {
return (
<ColorContext.Consumer>
{/*
Consumer 태그를 열고 중괄호를 열어서 이 사이에 함수를 넣어주었다.
이러한 패턴으로 코드를 작성하는 것을 Function as a child 혹은 Render Props라고 한다.
컴포넌트의 JSX가 있어야 할 자리에 문자열이 아닌 함수 자체를 전달하는 것
*/}
{(value) => (
<div
style={{
width: "64px",
height: "64px",
background: value.color,
}}
></div>
)}
</ColorContext.Consumer>
);
};
export default ColorBox;
- ColorContext 로부터 값을 받아 시각적으로 보여주는 데 사용할 컴포넌트를 하나 생성합니다.
- ColorContext 로부터 값을 수신하기 위해서는 <ColorContext.Consumer/> 라는 태그로 감싸여야 합니다.
- 위의 ColorBox.js에서는 독특한 문법을 하나 볼 수 있는데, 분명 <ColorContext.Consumer/> 라는 태그도 JSX에 일종일 텐데 children 영역에 어떤 값이나 태그가 아닌 함수 가 삽입이 가능한 것을 확인할 수 있습니다. 이렇게 작성하는 패턴을 function as child 또는 Render props 라고 부르며, Context 에서 뿐만 아니라 다른 일반적인 컴포넌트에서도 이러한 패턴을 사용할 수 있습니다.
App.js
import "./App.css";
import "./Custom.css";
//컴포넌트 삽입
import ColorBox from "./components/ColorBox";
import ColorContext from "./contexts/color";
const App = () => {
return (
<>
<h1>기본 context 사용하기</h1>
<div className="color-div">
{/* Context를 설정하는 첫번째 방법! */}
<ColorBox />
</div>
</>
);
};
export default App;
- 위와 같이 App.js에 ColorBox 태그를 입력하게 되면 아래와 같이 컴포넌트가 정상 렌더링 된 것을 확인할 수 있습니다.
Context Provider
- Context 의 값을 props처럼 변경하여 동적으로 사용하고 싶다면 아래와 같이 사용할 수 있습니다.
App.js
import "./App.css";
import "./Custom.css";
//컴포넌트 삽입
import ColorBox from "./components/ColorBox";
import ColorContext from "./contexts/color";
const App = () => {
return (
<>
<h1>기본 context 사용하기</h1>
<div className="color-div">
{/* Context를 설정하는 첫번째 방법! */}
<ColorBox />
<ColorContext.Provider value={{ color: "red" }}>
<ColorBox />
</ColorContext.Provider>
</div>
</>
);
};
export default App;
- Provider를 통해 값이 변경된 컴포넌트는 아래와 같이 다른 색상으로 보입니다.
동적으로 Context 사용하기
- Context 는 위 처럼 이미 작성된 것을 사용해도 되지만, 개발자가 별도로 커스터마이징 하여 사용 가능합니다.
- Context 의 Consumer와 Provider는 아래와 같이 커스터마이징 가능합니다.
color2.js
import { createContext, useState } from "react";
// createContext를 통해 생성하는 ColorContext의 state 값은
// 아래의 ColorProvider에서 실제로 사용할 객체의 기본 값으로 일치시켜주는 것이 좋다.
// 나중에 실수해서 값을 안넣어도 오류 안나기도 하고, 코드를 분석하는데도 도움을 준다.
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;
// ColorProvider, ColorConsumer 내보내기
export { ColorProvider, ColorConsumer };
export default ColorContext;
Context Consumer
ColorBox3.js
// Hook을 사용한 ColorBox
import { useContext } from "react";
import ColorContext from "../contexts/color2";
const ColorBox3 = () => {
const { state } = useContext(ColorContext);
return (
<>
<div
className="color-box"
style={{
background: state.color,
}}
/>
<div
className="color-box"
style={{
background: state.subColor,
}}
/>
</>
);
};
export default ColorBox3;
- 함수형 컴포넌트라면 useContext 라는 Hook을 통해 context를 조금 더 편하게 사용 가능합니다.
일반 컴포넌트 (SelectColor)
- 여러가지 색상 팔레트를 선택하면 그 색상으로 다른 컴포넌트의 색상을 바꿔보겠습니다.
- 이를 위해 일종의 리모컨의 역할을 할 일반 컴포넌트 (SelectColor)를 생성합니다.
- 여기서 발생한 이벤트로 ColorBox3 컴포넌트 색상을 바꿔보도록 하겠습니다.
import { ColorConsumer } from "../contexts/color2";
const colors = ["red", "orangered", "orange", "yellow", "green", "blue", "skyblue", "navy", "violet", "pink"];
const SelectColor = () => {
return (
<>
<div>
<h1>색상을 선택하세요.</h1>
<ColorConsumer>
{({ actions }) => (
<>
<div style={{ display: "flex" }}>
{colors.map((color) => (
<div
key={color}
className="color-box"
style={{
background: color,
cursor: "pointer",
}}
onClick={() => actions.setColor(color)}
onContextMenu={(e) => {
e.preventDefault(); // 마우스 오른쪽 버튼 클릭시 메뉴가 뜨는 것을 방지함
actions.setSubColor(color);
}}
/>
))}
</div>
</>
)}
</ColorConsumer>
<hr />
</div>
</>
);
};
export default SelectColor;
App.js
import "./App.css";
import "./Custom.css";
//컴포넌트 삽입
import ColorBox from "./components/ColorBox";
import ColorContext from "./contexts/color";
// color2로부터 ColorProvider를 최상위 컴포넌트에 삽입
import { ColorProvider } from "./contexts/color2";
// colorBox2를 삽입
import ColorBox2 from "./components/ColorBox2";
// SelectColor 컴포넌트를 삽입
import SelectColor from "./components/SelectColor";
동적으로 context의 값이 변하면 다른 컴포넌트에 반영되는 모습
'프론트엔드(Front-End) > React' 카테고리의 다른 글
[REACT] Immer와 불변성 (0) | 2023.01.13 |
---|---|
[REACT] 리액트와 라우터(Router) (2) | 2023.01.09 |
[REACT] 리액트와 훅(HOOK) (0) | 2023.01.08 |
[REACT] 리액트의 라이프사이클(LifeCycle) (0) | 2023.01.07 |
[REACT] 컴포넌트 요소의 반복(map, filter) (0) | 2023.01.06 |