React Context 사용법
리액트 앱을 개발하다보면 모든 컴포넌트에 어떤 값을 props
으로 넘기고 싶은 데이터가 필요할 때가 있습니다.
다시 말해, 어떤 컴포넌트 트리 내에서 최상위 컴포넌트 부터 최말단 컴포넌트에 걸쳐 전역(global)으로 관리해야 할 데이터가 필요한 상황이 되겠네요.
이번 포스팅에서는 React Context를 활용하여 리액트 앱에서 전역 데이터를 관리하는 방법에 대해서 알아보도록 하겠습니다.
React Context 없이 개발하기
먼저 React Context 없이 props
만을 이용해서 어떻게 전역 데이터를 여러 컴포넌트에 걸쳐서 접근할 수 있는지에 대해서 살펴보도록 하겠습니다.
예를 들어, 다음과 같이 사용자가 설정한 언어에 따라서 영어 또는 한국어로 텍스트를 표시해주는 앱이 있다고 생각해봅시다.
import React, { Component, Fragment } from "react";
import Button from "./Button";
import Title from "./Title";
import Message from "./Message";
class App extends Component {
state = { lang: "en" };
toggleLang = () => {
this.setState(({ lang }) => ({
lang: lang === "en" ? "kr" : "en",
}));
};
render() {
const { lang } = this.state;
return (
<Fragment>
<Button lang={lang} toggleLang={this.toggleLang} />
<Title lang={lang} />
<Message lang={lang} />
</Fragment>
);
}
}
위 리액트 앱은 Button
, Title
, Message
이렇게 3개의 컴토넌트로 구성되어 있습니다.
최상위 앱의 state
에 저장되어 있는 사용자 언어 설정값 lang
은 모든 하위 컴포넌트에 props
으로 전달되고 있습니다.
Button
컴포넌트에는 추가적으로 toggleLang
함수값도 넘기고 있는데, 이 함수는 사용자 언어를 en
또는 kr
로 번갈아 가면서 설정하는데 쓰이게 됩니다.
하위 컴포넌트
3개의 하위 컴포넌트는 다음과 같이 props
로 넘어온 lang
값에 따라서 영어와 한국어의 텍스트를 렌더링합니다.
Button
컴포넌트는 toggleLang
함수값을 onClick
핸들러에 설정해줌으로써 버튼 클릭 시 사용자 언어 설정값이 바뀌도록 합니다.
Message
컴포넌트는 클래스로 구현해보았는데, 아래에서 React Context 사용법을 보여주기 위합입니다.
import React, { Component } from "react";
function Button({ lang, toggleLang }) {
return <button onClick={toggleLang}>{lang}</button>;
}
function Title({ lang }) {
const text = lang === "en" ? "Context" : "컨텍스트";
return <h1>{text}</h1>;
}
class Message extends Component {
render() {
const { lang } = this.props;
if (lang === "en")
return (
<p>
"Context provides a way to pass data through the component tree
without having to pass props down manually at every level"
</p>
);
else
return (
<p>
"컨텍스트는 모든 레벨에서 일일이 props를 넘기지 않고도 컴포넌트 트리에
걸쳐서 데이터를 전달할 수 있는 방법을 제공합니다."
</p>
);
}
}
문제점
위 코드는 모든 컴포넌트에서 사용자 언어 설정값을 전달하기 위해서 props
를 사용하고 있습니다.
예제에서는 컴포넌트 수가 몇개 되지 않게 때문에 크게 번거롭지 않아보이지만, 실제 수십, 수백개의 컴포넌트로 이뤄진 리액트 앱에서는 어떨까요?
특히, 다국어를 지원하지 않던 앱에서 갑자기 다국어를 지원해야하는 상황을 생각해보면 일일이 모든 컴포넌트 props
을 추가해주는 게 매우 끔찍할 것입니다.
React Context를 사용해서 개발하기
위에서 살펴본 것 처럼 큰 규모의 앱에서는 전역 데이터를 관리하는데 좀 더 나은 접근 방식이 필요합니다. React Context는 전역 데이터를 좀 더 단순하지만 체계적인 방식으로 접근할 수 있도록 도와줍니다.
지금부터 위에서 작성한 리액트 앱을 React Context를 사용해서 코드 리팩토링 해보면서 그 효과를 살펴보겠습니다.
Context 생성하기
전역 데이터를 관리하기 위해서 React 패키지에서 제공하는 createContext
라는 함수를 사용합니다.
개념적으로 React Context는 전역 데이터를 담고 있는 하나의 저장 공간이라고 생각할 수 있습니다.
다음과 같이 createContext
함수의 인자로 해당 컨텍스트에 디폴트로 저장할 값을 넘깁니다.
import { createContext } from "react";
const LangContext = createContext("en");
Context 저장하기
다음과 같이 어떤 컴포넌트에서 Provider
로 감싸주면, 그 하위에 있는 모든 컴포넌트들은 이 React Context에 저장되어 있는 전역 데이터에 접근할 수 있습니다.
value
속성값을 지정하지 않을 경우, Context를 생성할 때 넘겼던 디폴트값이 사용됩니다.
기존 앱에서는 모든 하위 컴포넌트에 props
로 사용자 언어 설정값인 lang
을 넘겨줘었는데, 바뀐 코드에서는 대신에 Provider
로 한번 감싸고 value
로 이 값을 설정해주고 있습니다.
import React, { Component } from "react";
import LangContext from "./LangContext";
import Button from "./Button";
import Title from "./Title";
import Message from "./Message";
class App extends Component {
state = { lang: "en" };
toggleLang = () => {
this.setState(({ lang }) => ({
lang: lang === "en" ? "kr" : "en",
}));
};
render() {
const { lang } = this.state;
return (
<LangContext.Provider value={lang}>
<Button toggleLang={this.toggleLang} />
<Title />
<Message />
</LangContext.Provider>
);
}
}
자 그럼 이제 상위 컴포넌트에서 Provider
를 이용하여 저장한 전역 데이터를 하위 컴포넌트에서 접근할 수 있습니다.
크게 3가지 방법으로 접근을 할 수 있는데 하나씩 알아보겠습니다.
Consumer로 Context 접근하기
먼저 Provider
와 대응하는 Consumer
를 이용하여 Context에 저장되어 있는 전역 데이터에 접근할 수 있습니다.
Consumer
는 render props
을 받기 때문에, Title
컴포넌트는 children
으로 넘기는 함수의 인자로 사용자 언어 설정값을 읽습니다.
import React from "react";
import LangContext from "./LangContext";
function Title() {
return (
<LangContext.Consumer>
{(lang) => {
const text = lang === "en" ? "Context" : "컨텍스트";
return <h1>{text}</h1>;
}}
</LangContext.Consumer>
);
}
useContext로 Context 접근하기
React Hooks에서 추가된 useContext
함수를 이용하면 좀 더 깔끔하게 Context에 저장되어 있는 전역 데이터에 접근할 수 있습니다.
Button
컴포넌트는 useContext
함수에 LangContext
를 넘김으로써 사용자 언어 설정값을 읽습니다.
단, 이 방법은 함수 컴포넌트에서만 사용 가능합니다.
import React, { useContext } from "react";
import LangContext from "./LangContext";
function Button({ toggleLang }) {
const lang = useContext(LangContext);
return <button onClick={toggleLang}>{lang}</button>;
}
contextType으로 Context 접근하기
Message
컴포넌트와 같이 클래스로 구현된 컴포넌트의 경우에는 contextType
을 사용해서 Context에 저장되어 있는 전역 데이터에 접근할 수 있습니다.
단, 이 방법은 클래스 컴포넌트에서만 사용 가능합니다.
import React, { Component } from "react";
import LangContext from "./LangContext";
class Message extends Component {
static contextType = LangContext;
render() {
const lang = this.context;
if (lang === "en")
return (
<p>
"Context provides a way to pass data through the component tree
without having to pass props down manually at every level"
</p>
);
else
return (
<p>
"컨텍스트는 모든 레벨에서 일일이 props를 넘기지 않고도 컴포넌트 트리에
걸쳐서 데이터를 전달할 수 있는 방법을 제공합니다."
</p>
);
}
}
주의 사항
모든 기능이 그러하듯 React Context는 꼭 본연의 용도에 맞는 상황에서만 사용해야 합니다. 즉, 전역 데이터를 한 곳에서 저장하고 여러 컴포넌트에서 접근하고 싶은 경우가 아니라 사용을 피해야 합니다. 왜냐하면 React Context를 사용하게 되면 해당 컴포넌트는 해당 Context가 없이는 재사용이 어렵기 때문입니다.
마치면서
이상으로 React Context를 왜 사용하고 어떻게 사용하는지에 대해서 살펴보았습니다. 전체 코드는 아래를 참고 바랍니다.