-
Context
context를 사용하면 컴포넌트 트리 전체에 데이터를 공유하여, 부모부터 자식에게 데이터를 props를 통해 전달해주지 않아도 됩니다.
언제 context를 사용해야하나요?
리액트를 사용하다면 현재 로그인한 유저, 테마(dark 혹은 light), 선호하는 언어 등 전역적으로 데이터를 공유해야 할 경우 context를 사용하면 됩니다.
context를 사용하지 않은 예시입니다.
class App extends React.Component { render() { return <Toolbar theme="dark" />; } } function Toolbar(props) { // Toolbar 컴포넌트는 불필요한 테마 prop를 받아서 // ThemeButton에 전달해야 합니다. // 앱 안의 모든 버튼이 테마를 알아야 한다면 // 이 정보를 일일이 넘기는 과정은 매우 곤혹스러울 수 있습니다. return ( <div> <ThemedButton theme={props.theme} /> </div> ); } class ThemedButton extends React.Component { render() { return <Button theme={this.props.theme} />; } }
Button만에게 현재 theme을 주어서 괜찮아 보이지만 button 뿐만 아닌 다른 컴포넌트에게 하나하나 정보를 줘야한다고 생각하면 불편할 수 있습니다.
context를 사용한 예시입니다.
// context를 사용하면 모든 컴포넌트를 일일이 통하지 않고도 // 원하는 값을 컴포넌트 트리 깊숙한 곳까지 보낼 수 있습니다. // light를 기본값으로 하는 테마 context를 만들어 봅시다. const ThemeContext = React.createContext('light'); class App extends React.Component { render() { // Provider를 이용해 하위 트리에 테마 값을 보내줍니다. // 아무리 깊숙히 있어도, 모든 컴포넌트가 이 값을 읽을 수 있습니다. // 아래 예시에서는 dark를 현재 선택된 테마 값으로 보내고 있습니다. return ( <ThemeContext.Provider value="dark"> <Toolbar /> </ThemeContext.Provider> ); } } // 이젠 중간에 있는 컴포넌트가 일일이 테마를 넘겨줄 필요가 없습니다. function Toolbar() { return ( <div> <ThemedButton /> </div> ); } class ThemedButton extends React.Component { // 현재 선택된 테마 값을 읽기 위해 contextType을 지정합니다. // React는 가장 가까이 있는 테마 Provider를 찾아 그 값을 사용할 것입니다. // 이 예시에서 현재 선택된 테마는 dark입니다. static contextType = ThemeContext; render() { return <Button theme={this.context} />; } }
context를 사용하여 중간단계에 있는 컴포넌트를 안줘도 마지막 child에서 테마값을 받아 적용할 수가 있습니다.
API
React.createContext
const MyContext = React.createContext(defaultValue);
- Context 객체를 만듭니다.
- 리액트가 해당 context 객체를 구독하는 컴포넌트를 랜더링하는 경우, 상위 트리에서 나한테 가장 가까운 context 값을 Provider로부터 받아옵니다.
- defaultValue는 트리 안에서 알맞은 Provider을 찾지 못할 때 사용됩니다.
Context.Provider
<MyContext.Provider value={/* 어떤 값 */}>
- Context.Provider은 context를 구독하는 컴포넌트들에게 context의 변화를 알립니다.
- Provider 컴포넌트는 value prop을 받아 하위 컴퍼넌트에게 전달합니다.
- Provider 하위에 다른 Provider을 배치 (하위 Provider의 값이 우선됩니다.)하는 것도 가능합니다. 이를 통해 하위 컴포넌트가 여러 context를 구독하게 만들 수 있습니다.
- value에 객체를 보내는 경우 변화 여부를 체크하는 알고리즘에 문제가 생길 수 있어 권장하지 않습니다.
Context.Consumer
<MyContext.Consumer> {value => /* context 값을 이용한 렌더링 */} </MyContext.Consumer>
- context 변화를 구독하는 컴포넌트입니다.
- 함수 컴포넌트 안에서 context를 구독할 수 있습니다.
Class.contextType
class MyClass extends React.Component { componentDidMount() { let value = this.context; /* MyContext의 값을 이용한 코드 */ } componentDidUpdate() { let value = this.context; /* ... */ } componentWillUnmount() { let value = this.context; /* ... */ } render() { let value = this.context; /* ... */ } } MyClass.contextType = MyContext;
- 원하는 클래스의 contextType 프로퍼티로 지정할 수 있습니다.
- 클래스 안에서 this.context를 이용해 Provider를 찾아 해당 값을 읽을 수 있게 합니다.
class MyClass extends React.Component { static contextType = MyContext; render() { let value = this.context; /* context 값을 이용한 렌더링 */ } }
- 클래스 안에 static contextType = MyContext 방식으로도 사용가능합니다.
예시
./context/color.js
import { createContext, useState } from "react"; const ColorContext = createContext({ state: { backgroundColor: "black", fontColor: "white" }, actions: { setBackgroundColor: () => {}, setFontColor: () => {}, }, }); const ColorProvider = ({ children }) => { const [backgroundColor, setBackgroundColor] = useState("black"); const [fontColor, setFontColor] = useState("white"); const value = { state: { backgroundColor, fontColor }, actions: { setBackgroundColor, setFontColor }, }; return ( <ColorContext.Provider value={value}>{children}</ColorContext.Provider> ); }; const { ColorConsumer } = ColorContext.Consumer; export { ColorProvider, ColorConsumer }; export default ColorContext;
우선 createContext()를 통해 context 객체를 생성합니다.
ColorContext.Provider은 따로 컴포넌트를 만들어 커스텀을 했습니다.
ColorContext.Consumer을 통해 ColorConsumer이 해당 context를 구독하도록 했습니다.
App.js
import React from "react"; import Main from "./components/Main"; import SelectColors from "./components/SelectColors"; import { ColorProvider } from "./contexts/color"; const App = () => { return ( <ColorProvider> <> <SelectColors /> <Main /> </> </ColorProvider> ); }; export default App;
App.js에서 ColorProvider 아래에 context를 구독할 컴포넌트를 작성합니다. 아래 컴포넌트는 상위의 Provider을 구독할 것입니다.
./components/Main.js
import React from "react"; import ColorConsumer from "../contexts/color"; const Main = () => { return ( <ColorConsumer> {({ state }) => ( <> <div style={{ fontSize: "22px", background: state.backgroundColor, color: state.fontColor, }} > Lorem ipsum dolor sit amet consectetur adipisicing elit. Dolorum accusantium odit fuga totam neque. Earum praesentium facilis nisi amet quae velit autem reprehenderit quam tenetur ratione, consequuntur et consectetur asperiores? </div> </> )} </ColorConsumer> ); }; export default Main;
ColorConsumer은 ColorProvider의 매개변수 value객체를 구독합니다. 그중 {state}를 받아옵니다. style에서 state 내 backgroundColor와 fontColor의 값을 받아 올 수 있도록 합니다.
./components/SelectColors.js
import React from "react"; import ColorConsumer from "../contexts/color"; const colors = [ "black", "white", "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.setBackgroundColor(color)} onContextMenu={(e) => { e.preventDefault(); actions.setFontColor(color); }} /> ))} </div> )} </ColorConsumer> <hr /> </div> ); }; export default SelectColors;
위 컴포넌트는 ColorProvider에서 action을 구독합니다. onClick() 과 onContextMenu()를 통해 backgroundColor와 fontColor을 조작할 수 있도록 했습니다.
결과
우클릭을 하면 배경이 바뀌며, 좌클릭을 하면 글의 색이 바뀌는 것을 확인할 수 있습니다.
예시 보충할 점
현재 예시 내 Context.Provider의 value에 객체를 넣었습니다만, 공식문서에서는 이를 권장하지 않습니다. 어떻게 다시 수정해야할지 고민해보겠습니다!
참고자료
Context – React
A JavaScript library for building user interfaces
ko.reactjs.org
리액트를 다루는 기술 - YES24
리액트, 어떻게 활용하느냐가 중요하다!기본기를 꼼꼼하게! 효과적으로 활용하는 방법까지 다양하게 배우자!리액트를 이해하기 위한 핵심 개념은 물론이고 어떤 상황에서 어떻게 사용해야 하
www.yes24.com
'리액트' 카테고리의 다른 글
ref & useRef() & forwardRef() (0) 2021.09.11 Portals (0) 2021.09.08 Lists & Keys & Reconciliation (0) 2021.08.29 JSX & createElement() & reactDOM.render() (0) 2021.08.24 Hooks (useState, useEffect) (0) 2021.08.23