리액트를 다루는 기술 ( Context API )
Context API : 리액트 프로젝트에서 전역적으로 사용할 데이터가 있을 때 유용한 기능
- 종류 : 사용자 로그인 정보 , 애플리케이션 환경 설정, 테마 등
- 리액트 관련 라이브러리에서 많이 사용
- 예) 리덕스 , 리액트 라우터 , styled-components 등
실습 흐름
- Context API를 사용한 전역 상태 관리 흐름 이해
- 기본적인 사용법
- 동적 Context 사용
- Consumer 대신 Hook 또는 static contextType 사용
15-1. Context API를 사용한 전역 상태 관리 흐름 이해
G 컴포넌트는 전역 상태를 업데이트 시키고 , F 와 J 컴포넌트는 업데이트 된 상태를 렌더링한다.
Root 컴포넌트의 state에는 value라는 값이 있고, 이 값을 변경시키는 handleSetValue라는 함수가 있다고 가정한다.
이러한 상황에선 , value 값과 handleSetValue 함수를 props 로 하위 컴포넌트한테 전달해주게 된다.
예를 들면 ,
value 값은 Root → A > B > F , Root → H → J 와 같은 형식으로
handleSetValue 값은 Root → A → B → E → G 와 같이 진행
실제 프로젝트에서는 컴포넌트의 깊이가 더 깊어 질 수도 있고, 다루게 되는 데이터들도 훨씬 많아 질 수도 있어서 이렇게 하면 유지보수성이 낮아질 가능성이 존재한다.
그렇기 때문에 Redux 나 MobX 같은 라이브러리를 사용한다고 한다. 하지만 Context API를 통하여 글로벌 상태 관리를 편하게 할 수 있다.
더 이상 여러 컴포넌트를 거쳐서 값을 전달해주는 것이 아니라, Context를 통해 원하는 값이나 함수를 바로 값을 받아와 사용할 수 있다.
15-2. Context API 사용법 익히기
yarn create react-app context-tutorial
15-2-1. 새 Context 만들기
yarn create react-app context-tutorial
src/contexts/color.js
import { createContext } from "react";
const ColorContext = createContext({ color: "black" });
export default ColorContext;
새 Context를 만들 떄는 createContext 함수를 사용한다. 파라미터에는 해당 Context의 기본 상태를 지정한다.
15-2-2. Consumer 사용하기
src/components/ColorBox.js
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;
ColorBox라는 컴포넌트를 만들어서 ColorContext 안에 들어 있는 색상을 보여준다. 이때 색상을 props로 받아 오는 것이 아니라 ColorContext 안에 들어있는 Consumer라는 컴포넌트를 통해 색상을 조회한다.
Consumer 사이에 중괄호를 열어서 그 안에 함수를 넣어 준다. 이러한 패턴을 Function as a child 또는 Render Props 라고 한다. ( 컴포넌트의 children이 있어야 할 자리에 일반 JSX 혹은 문자열이 아닌 함수를 전달하는 것 )
App.js 렌더링
import React from "react";
import ColorBox from "./components/ColorBox";
const App = () => {
return (
<div>
<ColorBox />
</div>
);
};
export default App;
15-2-3. Provider
Provider를 사용하면 Context의 value를 변경할 수 있다.
App.js
import React from "react";
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;
기존 createContext 함수를 사용할 때 파라미터로 Context의 기본값을 넣어 주었다. ( color: 'black' )
이 기본값은 Provider를 사용하지 않았을 때만 사용된다. 만약 Provider는 사용했는데 value를 명시하지 않았다면, 기본값을 사용하지 않기 때문에 오류가 발생한다.
15-3. 동적 Context 사용하기
지금까지는 고정적인 값만 사용할 수 있었지만, 이번에는 Context 값을 업데이트해야 하는 경우
15-3-1. Context 파일 수정하기
Context의 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 },
};
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.Provier를 렌더링하고 있다. 이 Provider의 value에 상태는 state로, 업데이트는 actions로 묶어서 전달하고 있다. 값을 동적으로 사용할 때 반드시 묶어서 줄 필요는 없지만, 객체를 따로 분리해 주면 나중에 다른 컴포넌트에서 Context의 값을 사용할 때 편하다.
createContext를 사용할 때 기본값으로 사용할 객체도 수정했다. createContext의 기본 값은 실제 Provider의 value에 넣는 객체의 형태와 일치시켜 주는 것이 좋다. 그렇게 하면 Context 코드를 볼 때 내부 값이 어떻게 구성되어 있는지 파악하기 쉽고, 실수로 Provider를 사용하지 않았을 때 리액트 애플리케이션에서 에러가 발생하지 않는다.
15-3-2. 새로워진 Context를 프로젝트에 반영하기
App 컴포넌트에 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도 마찬가지로 ColorContext.Consumer를 ColorConsumer로 변경 , 사용할 value의 형태도 바뀌었으니 이에 따른 변화를 반영
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: "64px",
height: "64px",
background: value.state.subcolor,
}}
/>
</>
)}
</ColorConsumer>
);
};
export default ColorBox;
객체 비구조화 할당 문법을 사용하면 value를 조회하는 것을 생략 가능
import React from "react";
import { ColorConsumer } from "../contexts/color";
const ColorBox = () => {
return (
<ColorConsumer>
{({ state }) => (
<>
<div
style={{
width: "64px",
height: "64px",
background: state.color,
}}
/>
<div
style={{
width: "32px",
height: "32px",
background: state.subcolor,
}}
/>
</>
)}
</ColorConsumer>
);
};
export default ColorBox;
15-3-3. 색상 선택 컴포넌트 만들기
Context의 actions에 넣어 준 함수를 호출하는 컴포넌트 생성
components/SelectColors.js
import React from "react";
const colors = ["red", "orange", "yellow", "green", "blue", "indigo", "violet"];
const SelectColors = () => {
return (
<div>
<h2>색상을 선택하세요.</h2>
<div style={{ display: "flex" }}>
{colors.map((color) => (
<div
key={color}
style={{
background: color,
width: 24,
height: 24,
cursor: "pointer",
}}
/>
))}
</div>
<hr />
</div>
);
};
export default SelectColors;
App 컴포넌트에 렌더링
import React from "react";
import ColorBox from "./components/ColorBox";
import { ColorProvider } from "./contexts/color";
import SelectColors from "./contexts/SelectColors";
const App = () => {
return (
<ColorProvider>
<div>
<SelectColors />
<ColorBox />
</div>
</ColorProvider>
);
};
export default App;
이제 해당 SelectColors에서 마우스 왼쪽 버튼을 클릭하면 큰 정사각형의 색상을 변경, 오른쪽 버튼을 클릭하면 작은 정사각형의 색상을 변경하도록 구현
import React from "react";
import { 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: 24,
height: 24,
cursor: "pointer",
}}
onClick={() => actions.setColor(color)}
onContextMenu={(e) => {
e.preventDefault(); // 오른쪽 버튼 클릭 시 메뉴가 뜨는 것을 무시
actions.setSubColor(color);
}}
/>
))}
</div>
)}
</ColorConsumer>
<hr />
</div>
);
};
export default SelectColors;
마우스 오른쪽 버튼 클식 이벤트는 onContextMenu를 사용하면 된다. 원래 브라우저 메뉴가 나타나지만, 여기서 e.preventDefault()를 호출하면 메뉴가 뜨지 않는다.
15-4. Consumer 대신 Hook 또는 static contextType 사용
Context에 있는 값을 사용할 때 Consumer 대신 다른 방식을 사용하여 값을 받아 오는 방법
15-4-1. useContext Hook 사용
useContext( 리액트 내장 Hooks )라는 Hook을 사용하면 함수형 컴포넌트에서 COntext를 편하게 사용할 수 있다.
components/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;
children에 함수를 전달하는 Render Props 패턴이 불편하다면 , useContext Hook을 사용하여 편하게 Context 값을 조회할 수 있다.
단! Hook은 함수형 컴포넌트에서만 사용할 수 있다는 점에 주의!
15-4-2. static contextType 사용
이번엔 클래스형 컴포넌트로 알아볼텐데, Context를 쉽게 사용하고 싶다면 static contextType을 정의하는 방법이 있다. SelectColors 컴포넌트를 클래스형으로 변환하고, Consumer 코드는 일단 제거한다.
import React, { Component } from "react";
const colors = ["red", "orange", "yellow", "green", "blue", "indigo", "violet"];
class SelectColors extends Component {
render() {
return (
<div>
<h2>색상을 선택하세요.</h2>
<div style={{ display: "flex" }}>
{colors.map((color) => (
<div
key={color}
style={{
background: color,
width: 24,
height: 24,
cursor: "pointer",
}}
/>
))}
</div>
<hr />
</div>
);
}
}
export default SelectColors;
클래스 상단에 static contextType 값을 지정
import React, { Component } from "react";
import ColorContext from "../contexts/color";
const colors = ["red", "orange", "yellow", "green", "blue", "indigo", "violet"];
class SelectColors extends Component {
static contextType = ColorContext;
render() {
return (
<div>
<h2>색상을 선택하세요.</h2>
<div style={{ display: "flex" }}>
{colors.map((color) => (
<div
key={color}
style={{
background: color,
width: 24,
height: 24,
cursor: "pointer",
}}
/>
))}
</div>
<hr />
</div>
);
}
}
export default SelectColors;
이렇게 하면 this.context를 조회했을 떄 현재 Context의 value 값을 가리키게 된다.
만약, setColor를 호출하고 싶다면 this.context.actions.setColor를 호출하면 된다.
import React, { Component } from "react";
import ColorContext from "../contexts/color";
const colors = ["red", "orange", "yellow", "green", "blue", "indigo", "violet"];
class SelectColors extends Component {
static contextType = ColorContext;
handleSetColor = (color) => {
this.context.actions.setColor(color);
};
handleSetSubcolor = (subcolor) => {
this.context.actions.setSubColor(subcolor);
};
render() {
return (
<div>
<h2>색상을 선택하세요.</h2>
<div style={{ display: "flex" }}>
{colors.map((color) => (
<div
key={color}
style={{
background: color,
width: 24,
height: 24,
cursor: "pointer",
}}
onClick={() => this.handleSetColor(color)}
onContextMenu={(e) => {
e.preventDefault();
this.handleSetSubcolor(color);
}}
/>
))}
</div>
<hr />
</div>
);
}
}
export default SelectColors;
static contextType을 정의하면
- 장점 : 클래스 메서드에서도 Context에 넣어 둔 함수를 호출할 수 있다.
- 단점 : 한 클래스에서 하나의 Context밖에 사용하지 못한다.
그러나 앞으로 새로운 컴포넌트를 작성할 때 클래스형으로 작성하는 일은 많지 않기 때문에 useContext를 사용하는 쪽을 권한다.
Author And Source
이 문제에 관하여(리액트를 다루는 기술 ( Context API )), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@dohy9443/리액트를-다루는-기술-Context-API저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)