안녕하세요! delay100입니다. 이번 포스팅에서는 Context API에 대해 공부해봅시다.
대부분의 설명은 주석으로 달아놓았으니 코드에 대한 설명은 주석을 확인해주세요!
책 리액트를 다루는 기술, 개정판의 15장 내용을 다루고 있습니다.
이번 포스팅의 Github 링크
https://github.com/delay-100/study-react/tree/main/ch15/context-tutorial
1. Context API?
기존에는 컴포넌트 간에 상태 교류 시 무조건 부모에서 자식으로 props를 통해 전달해주었습니다. 이제는 Context API를 통해 더욱 쉽게 상태를 교류할 수 있습니다.
Context API란?
- 리액트 프로젝트에서 전역적으로 사용할 데이터가 있을 때 유용한 기능(ex. 사용자 로그인 정보, 애플리케이션 환경 설정, 테마 등)
- 리덕스, 리액트 라우터, styled-components 등의 라이브러리가 Context API를 기반으로 구현 되어있음
- 전역적으로 여기저기서 사용되는 상태가 있고 컴포넌트의 개수가 많은 상황에 사용하면 좋음
- 프로젝트 컴포넌트 구조가 꽤 간단하고 다루는 상태의 종류가 많지 않다면 굳이 Context를 사용할 필요는 없음
2. Context API 사용하기
2-1. 시작하기
src 디렉터리 안에 contexts 디렉터리를 생성한 뒤 아래의 파일(color.js)을 만들어줍시다.
Context 사용 시 디렉터리의 이름 등은 contexts말고 다른 경로를 해도 됩니다. 여기서는 다른 파일들과 구분하기 위해 만들어주었습니다.
- Context API 기본 사용 예제 - src/contexts/color.js
import { createContext } from "react";
const ColorContext = createContext({ color: "black" }); // 새 Context를 만들 때는 createContext 함수를 사용. 파라미터에는 Context의 기본 상태를 지정
export default ColorContext;
이어서 components 디렉터리도 생성후 아래의 파일을 만들어주었습니다.
- Context API 기본 사용 예제 - src/components/ColorBox.js
// ColorBox 컴포넌트: ColorContext 안에 들어 있는 색상을 보여주는 컴포넌트
import ColorContext from "../contexts/color";
const ColorBox = () => {
return (
<ColorContext.Consumer>
{/* Function as a child(Render Props): Consumer 사이에 중괄호를 열어서 그 안에 함수를 넣어 주는 것
컴포넌트의 child가 있어야 할 자리에 일반 JSX 혹은 문자열이 아닌 함수를 전달하는 것*/}
{(value) => (
<div
style={{
width: "64px",
height: "64px",
background: value.color,
}}
/>
)}
</ColorContext.Consumer>
);
};
export default ColorBox;
- Context API 기본 사용 예제 - src/App.js
import ColorBox from "./components/ColorBox";
import ColorContext from "./contexts/color";
const App = () => {
return (
<ColorContext.Provider value={{ color: "red" }}>
{/*Provider 사용 시 Context의 value를 바꿀 수 있음. Provider을 사용한다면 value를 꼭 넣어줘야 함 -> 미기재시 에러 */}
<div>
<ColorBox />
</div>
</ColorContext.Provider>
);
};
export default App;
2-2. 동적 Context
2-1에서는 고정적인 값만 사용할 수 있었습니다. 이번에는 Context의 값을 업데이트해야 하는 경우를 다뤄봅시다.
- 동적 Context 사용 예제 - src/contexts/dynamiccolor.js
import { createContext, useState } from "react";
const ColorContext = createContext({
// createContext의 기본값은 실제 Provider의 value에 넣는 객체의 형태와 일치시켜 주는 것이 좋음
state: { color: "black", subcolor: "red" },
actions: {
setColor: () => {},
setSubcolor: () => {},
},
}); // 새 Context를 만들 때는 createContext 함수를 사용. Context의 value에는 상태 값 뿐만아니라 함수를 전달할 수도 있음
// ColorProvider 컴포넌트
const ColorProvider = ({ children }) => {
const [color, setColor] = useState("black");
const [subcolor, setSubcolor] = useState("red");
const value = {
// Provider의 value에는 상태는 state, 업데이트 함수는 actions로 묶어서 전달, 반드시 묶어줄 필요는 없지만 state와 actions 객체를 따로 분리해주면 나중에 다른 컴포넌트에서 Context 값 사용시 편리함
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 사용 예제 - src/components/DynamicColorBox.js
// DynamicColorBox 컴포넌트: ColorContext 안에 들어 있는 색상을 보여주는 컴포넌트
import { ColorConsumer } from "../contexts/dynamiccolor";
const DynamicColorBox = () => {
return (
<ColorConsumer>
{/* Function as a child(Render Props): Consumer 사이에 중괄호를 열어서 그 안에 함수를 넣어 주는 것
컴포넌트의 child가 있어야 할 자리에 일반 JSX 혹은 문자열이 아닌 함수를 전달하는 것*/}
{(
{ state } // 비구조화 할당 사용 시 value 생략 가능
) => (
// {(value) => (
<>
<div
style={{
width: "64px",
height: "64px",
background: state.color,
// background: value.state.color,
}}
/>
<div
style={{
width: "32px",
height: "32px",
background: state.subcolor,
// background: value.state.subcolor,
}}
/>
</>
)}
</ColorConsumer>
);
};
export default DynamicColorBox;
- 동적 Context 사용 예제 - src/App.js
import DynamicColorBox from "./components/DynamicColorBox";
import { ColorProvider } from "./contexts/dynamiccolor";
const App = () => {
return (
<ColorProvider>
<div>
<DynamicColorBox />
</div>
</ColorProvider>
);
};
export default App;
2-3. 색상 선택 컴포넌트 만들기
이번에는 Context의 actions에 넣어 준 함수를 호출하는 컴포넌트를 만들어봅시다.
- 색상 선택 컴포넌트 예제 - src/components/SelectColors.js
import { ColorConsumer } from "../contexts/dynamicolor";
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;
- 색상 선택 컴포넌트 예제 - src/contexts/dynamiccolor.js
import { createContext, useState } from "react";
const ColorContext = createContext({
// createContext의 기본값은 실제 Provider의 value에 넣는 객체의 형태와 일치시켜 주는 것이 좋음
state: { color: "black", subcolor: "red" },
actions: {
setColor: () => {},
setSubcolor: () => {},
},
}); // 새 Context를 만들 때는 createContext 함수를 사용. Context의 value에는 상태 값 뿐만아니라 함수를 전달할 수도 있음
// ColorProvider 컴포넌트
const ColorProvider = ({ children }) => {
const [color, setColor] = useState("black");
const [subcolor, setSubcolor] = useState("red");
const value = {
// Provider의 value에는 상태는 state, 업데이트 함수는 actions로 묶어서 전달, 반드시 묶어줄 필요는 없지만 state와 actions 객체를 따로 분리해주면 나중에 다른 컴포넌트에서 Context 값 사용시 편리함
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;
- 색상 선택 컴포넌트 예제 - src/App.js
import DynamicColorBox from "./components/DynamicColorBox";
import { ColorProvider } from "./contexts/dynamiccolor";
import SelectColors from "./components/SelectColors";
const App = () => {
return (
<ColorProvider>
<div>
<SelectColors />
<DynamicColorBox />
</div>
</ColorProvider>
);
};
export default App;
App.js에 쓰인 DynamicColorBox 코드는 동적 Context 사용 예제 - src/components/DynamicColorBox.js 와 동일합니다.
- 마우스 왼쪽 버튼 클릭: 큰 정사각형의 색상 변경
- 마우스 오른쪽 버튼 클릭: 작은 정사각형의 색상 변경
이 아래 부터는 Context에 있는 값을 사용할 때 Consumer 대신 다른 방식을 사용하여 값을 받아오는 방법을 알아봅시다.
3. 함수 컴포넌트 - Hook 이용
리액트의 내장 Hooks 중에서 useContext라는 Hook 사용 시 함수 컴포넌트에서 Context를 아주 편하게 사용할 수 있습니다. 앞서 children에 함수를 전달하는 Render Props 패턴을 썼는데, Hook을 사용하면 더 쉽게 Context 값을 조회할 수 있습니다.
이 방식은 함수 컴포넌트에서만 사용 가능하고, 클래스형 컴포넌트에서는 사용이 불가합니다.
- useContext Hook 사용 예 - src/components/HookColorBox.js
// HookColorBox 컴포넌트: ColorContext 안에 들어 있는 색상을 보여주는 컴포넌트
import { useContext } from "react";
import ColorContext from "../contexts/dynamiccolor";
const HookColorBox = () => {
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 HookColorBox;
2-3 코드 중에서 DynamicColorBox.js만 위의 코드로 변경한다면, 2-3과 같은 실행 결과를 볼 수 있습니다.
4. 클래스형 컴포넌트 - static contextType 이용
이번에는 클래스형 컴포넌트에서 Context를 더 쉽게 사용하는 방법을 알아봅시다.
static contextType을 정의하는 방법입니다.
- 장점: 클래스 메서드에서도 Context에 넣어 둔 함수를 호출할 수 있음
- 단점: 한 클래스에서 하나의 Context밖에 사용하지 못함
- static contextType 사용 예 - src/components/ClassSelectColors.js
import { Component } from "react";
import ColorContext from "../contexts/color";
const colors = ["red", "orange", "yellow", "green", "blue", "indigo", "violet"];
class SelectColors extends Component {
static contextType = ColorContext; // this.context를 조회했을 때 현재 Context의 value를 가리키게 됨
// 만약 setColor를 호출하고 싶다면 this.context.actions.setColor을 호출하면 됨
handleSetColor = (color) => {
this.context.actions.setColor(color);
};
handleSetSubcolor = (subcolor) => {
this.context.actions.setSubcolor(subcolor);
};
render() {
return (
<div>
<h2>색상을 선택하세요.</h2>
{({ actions }) => (
<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>
)}
</div>
);
}
}
export default SelectColors;
2-3 코드 중에서 SelectColors.js만 위의 코드로 변경한다면, 2-3과 같은 실행 결과를 볼 수 있습니다.
그러나, 앞으로 새로운 컴포넌트를 작성 시 클래스형으로 작성하는 일은 거의 없기 때문에 useContext를 사용하는 것이 좋습니다.
읽어주셔서 감사합니다. 잘못된 정보는 댓글로 알려주세요!
'Study > React' 카테고리의 다른 글
17. Redux 사용하기(1) - 앱 상태 관리 (0) | 2022.08.03 |
---|---|
16. Redux(리덕스) 기본 (0) | 2022.07.31 |
14. 뉴스 뷰어 만들기(with. newsapi) (0) | 2022.07.27 |
13. Router(라우터) & SPA(Single Page Application) (0) | 2022.07.24 |
12. 불변성 유지하기(immer) (0) | 2022.07.21 |