Context는 모든 레벨의 컴포넌트 트리에 props를 통하지 않아도 데이터를 전달할 수 있는 방법을 제공해줍니다.
작은 프로젝트의 수준을 넘어서 꽤 규모가 있는 프로젝트를 만들고 싶거나 반복적으로 만들던 컴포넌트를 재사용 가능한 컴포넌트로 만들어 보고 싶다면 Context API가 필요한 순간이 올 것입니다.
기본적으로 리액트에서 다른 컴포넌트로부터 데이터를 전달 받을 때 props 라는 것을 사용합니다.
하지만 props는 한계가 있습니다. 자신의 바로 위에 있는 컴포넌트로부터 직접 전달 받아야 한다는 점입니다.
부모 컴포넌트가 있고 자식 컴포넌트가 밑에 있을 때 props로 넘겨줘야 합니다. 만약 이 depth가 한 번이라면 괜찮겠지만 depth가 깊다고 가정한다면 한참 위에 있는 부모 컴포넌트로부터 값을 전달 받아야 사용해야 합니다. 그러면 중간에 있는 컴포넌트들은 이 값을 별로 사용하고 싶지 않아도 자식 컴포넌트로 props를 넘겨주는 일을 해야 합니다.
이해하기 어려울 수 있어 그림으로 간단하게 설명해드리겠습니다.
그림과 같이 B,C에선 사용하지 않지만 D에서 상태를 사용해야 한다면 A → B → C → D 순으로 props를 넘겨줘야 합니다.
이것보다 depth가 더 길어지게 된다면 코드도 복잡해질 뿐만 아니라 유지보수에도 어려워지게 됩니다.
또 다른 예시를 하나 들어보겠습니다. 리액트는 SPA(Single Page Aplication)이기 때문에 항상 최상위 컴포넌트가 있습니다. 그리고 라우터 처리를 해서 페이지가 나눠지는 것처럼 만들 수 있습니다.
B라는 페이지 역할을 하는 컴포넌트와 C라는 페이지 역할을 하는 컴포넌트가 있다고 가정을 하겠습니다.
만약 D와 F 컴포넌트에서 필요한 데이터가 같다면 어떻게 해야할까요?
A라는 컴포넌트에 상태를 만들고 B와 C 각각에 props로 전달하고 쭉 타고 내려가 D와 F까지 상태를 전달해야할 것입니다.
이렇게 페이지가 많아지는 등 프로젝트 규모가 커진다면 props만으로 데이터를 전달하기 힘든 상황이 올 것입니다.
이럴 경우에 사용하면 좋은 것이 Context API
입니다.
context API를 사용하면 context를 만들고 특정 컴포넌트에 context를 감싸준다면 감싼 컴포넌트의 하위에 있는 모든 컴포넌트들은 해당 context에 직접적으로 접근하여 상태를 사용할 수 있게 됩니다.
아래의 사진과 같이 D와 F에서 모두 사용할 수 있도록 하려면 A라는 컴포넌트를 context로 감싸줘야겠죠?
코드로 예를 들어보겠습니다.
아래의 코드처럼 Title 컴포넌트와 User컴포넌트에 라우팅을 적용했습니다.
import React from "react";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import Title from "./pages/title";
import User from "./pages/user";
export default function App() {
return (
<Router>
<Switch>
<Route path="/title" component={Title} />
<Route path="/user" component={User} />
</Switch>
</Router>
);
}
만약 Title컴포넌트와 User컴포넌트에서 같은 상태를 가지고 싶다면 Context API로 어떻게 할 수 있을까요?
아래의 코드와 같이 createContext 함수를 통해 비어있는 context를 만들고 context의 Provider를 통해 상태를 넣을 수 있습니다. 아래의 코드에서는 간단한 유저 정보를 넣었습니다.
그리고 가운데 있는 props.children은 하위 컴포넌트가 들어갈 자리인데요. children에 들어가는 컴포넌트들은 UserContext로 감싸져있기 때문에 언제든지 context에 있는 상태에 접근할 수 있게 됩니다.
import React, { createContext } from "react";
export const UserContext = createContext();
const Store = (props) => {
const users = {
name: "jihoon",
age: 25
};
return (
<UserContext.Provider value={users}>{props.children}</UserContext.Provider>
);
};
export default Store;
그럼 방금 만든 Store를 App.js에 들어가 최상단에 감싸보겠습니다.
export default function App() {
return (
<Store>
<Router>
<Switch>
<Route path="/title" component={Title} />
<Route path="/user" component={User} />
</Switch>
</Router>
</Store>
);
}
그럼 하위 컴포넌트에서 context에 있는 상태를 가져다 쓰려면 어떻게 해야할까요?
아까 만들었던 UserContext와 react에서 제공하는 useContext 훅을 사용하면 됩니다.
아래와 같이 useContext의 인자로 사용할 context를 넣어주게 되면 아까 context를 만들 때 Provider에 넣어줬던 값을 반환하므로 비구조화 할당을 통해 원하는 값만 가져올 수 있습니다.
import React, { useContext } from "react";
import { UserContext } from "../store/user";
const Title = () => {
const { name } = useContext(UserContext);
return (
<>
<h1>hello {name}</h1>
</>
);
};
export default Title;
사진과 같이 이름을 잘 불러온 것을 볼 수 있습니다.
Context는 꼭 용도에 맞는 상황에서만 사용해야 합니다. 즉, 전역 데이터를 한 곳에서 저장하고 여러 컴포넌트에서 접근하고 싶은 경우가 아니라면 사용을 안하는게 좋습니다. 문서에 따르면 Context 를 사용하게 되면 해당 컴포넌트는 해당 Context가 없이는 재사용이 어려워지므로 꼭 필요 할때만 사용해야합니다.
이번 포스팅에선 Context API에 대해서 알아보았습니다. 다음엔 Redux에 대해서 알아보겠습니다.