Definition of Context API
이번 시간에는 더 큰 React 어플리케이션에서 마주칠 수 있는 다른 문제들에 대해서 알아보자.
해당 문제는 props를 통해 많은 component를 거쳐 많은 데이터를 전달할 때 발생한다.
이제까지 각 componenet에서 데이터를 상위, 하위의 데이터를 받아오는 방법을 배웠다.
Props Chains
아래의 시나리오 구조를 살펴보자.
해당 어플리케이션은 로그인 기능이 있으며, 쇼핑몰처럼 물건을 선택해서 카트에 저장할 수 있다.
로그인에 대한 정보는 LoginForm
component에서 사용자로 받아와서 state 형태로 저장될 것이다.
그 정보는 Shop과 Cart에서 사용되기에 전달해줘야한다.
하지만 데이터가 필요할 수도 있는 다른 component에 직접적으로 연결되지 않는다.
때문에 이제껏 배웠던 데이터 전달 방법에 따라 전달될 것이다.
- Shop : LoginForm -> Auth -> App -> Shop
- Cart : LoginForm -> Auth -> App -> Cart
Product component에서 상품을 Cart에 담는 것 또한 비슷한 구조로 데이터가 전달될 것이다.
그런 전달 방식은 아래와 같은 의존성을 띈다.
본질적으로 데이터를 필요로 하는 componenet에게 전달하기 위해서는 Auth, Shop, Cart와 간접적으로 연결되어 있는 App component까지 도달한 후에야 관리될 것이다.
이때에, 연결된 모든 component를 통해서 전달되는데 그 component가 해당 데이터를 직접 사용하지 않더라도 전달하기 위해서 요구되어진다.
앞서 설명한 문제가 여기에서 생긴다.
이러한 props chain은 데이터를 component를 통해 다른 component로 전달하기 위해 만들어진다.
하지만 앱이 커질수록 이런 방식은 불편해진다. 실수를 할 수 있는 영역이 커지며 개발자의 입장에서는 이것을 원하지 않는다.
대신 props로 전달되는 데이터는 실제 데이터가 필요한 component에서만 받아올 수 있으면 좋겠다.
이를 위해서 component 전체에서 사용할 수 있는 React 내부에 저장된(내장된) state 저장소가 존재한다.
그것을 React context라고 부른다.
Create Context
우선 기본적으로 사용하기 위해서 component와 경로를 분리하여 관리한다.
보통 state나 store라고 directory를 만들고 그 안에 context를 생성하게 된다.
component가 아니니 파스칼 표기법이 아닌 케밥 표기법으로 작성하겠다.
아래와 같이 기본 context를 생성할 수 있다.
// 📂 In src/store/auth-context.js
import React from "react";
// AuthContext는 component는 아니지만 component를 포함함
const AuthContext = React.createContext({
isLoggedIn: false
}) ;
export default AuthContext ;
여기서의 createContext()
를 사용하면 빈 state의 component를 출력해주기에 state가 어떻게 해야하는지 정해줘야 한다.
createContext()
의 parameter로는 기본값이 저장되며, 문자열과 같은 간단한 것을 넣을 수도 있지만, 대부분의 경우 객체를 사용한다.
이렇게 해서 얻는 결과는 component가 되거나 component를 포함하는 객체가 된다.
다른 파일에서 사용할 수 있도록 export로 감싼다. 이러면 특정 context 객체를 내보낼 수 있다.
Pre-working
어플리케이션에서 context를 사용하기 위해서는 2가지 작업이 수행되어야한다.
먼저 공급해야 한다. React에게 기본적으로 "여기 context가 있다"고 알려주는 것이다.
물론, 해당 context를 감싸는 모든 component는 context에 대한 접근 권한이 있어야 한다.
공급 이외에도 소비해야하는데, 연동하고 listening 해야한다.
Provider
항상 공급은 첫 번째로 하는 일이다. 공급한다는 것은, JSX 코드로 감싸는(wrap) 것을 뜻한다.
그 context를 활용할 수 있어야하는 모든 component들을 감싸야 한다.
감싸지지 않은 component들은 context를 listening 할 수 없다.
전체 애플리케이션의 모든 곳에서 필요하다면, 가능한 모든 component에서 즉, App component에서 감싸야 할 것이다.
만약 로그인 component와 그 자식 component에서만 필요하다면 로그인 component만 감싸면 된다.
return (
<AuthContext.Provider>
<MainHeader onLogout={logoutHandler} />
<main>
{!isLoggedIn && <Login onLogin={loginHandler} />}
{isLoggedIn && <Home onLogout={logoutHandler} />}
</main>
</AuthContext.Provider>
);
AuthContext
자체가 component가 되지 않을 것이지만 JSX 코드에서는 component로 접근이 필요하다.
하지만 AuthContext
에 점(.)을 찍어서 속성으로 접근할 수 있다. AuthContext.Provider
이것이 공급자이다.
AuthContext.Provider
는 component로 JSX 코드에서 사용할 수 있다.
그렇기에 다른 component들을 감쌀 수 있다.
하지만 그대로 사용하면 오류가 나는데 auth-context.js
파일에서 AuthContext
를 선언할 때 사용한 기본값은 Provider
없이 사용하는 기본값이기 때문이다.
Provider
가 필요하긴하나, 기본값이 있으면 Provider
는 필요없다.
하지만 기본값으로 사용하는 context는 동적으로 변할 수 있는 값을 사용할 수 없다.
때문에 Provider
에서 value
props를 추가해줄 수 있다. 이때, props의 이름이 다른 것이면 안된다.
<AuthContext.Provider
value={{
isLoggedIn: isLoggedIn,
}}
/>
이제 context를 필요로 하는 모든 component들이 감싸는 것으로 인해 해당 context에 접근할 수 있게 되었다.
Listening
두가지 방법으로 listening하여 값에 접근할 수 있다.
AuthContext.Consumer
: component functionuseContext()
: React Hook
일반적인 경우, React hook을 사용하지만 두가지 경우 모두 소개하겠다.
Consumer
이전에 .Provider
처럼 .Consumer
을 써서 감싸줄 수 있다.
Consumer
는 약간 다르게 작동하는데, 자식을 가지며 함수 형태로 사용한다.
import React from "react";
import classes from "./Navigation.module.css";
import AuthContext from "../../store/auth-context";
const Navigation = (props) => {
return (
<AuthContext.Consumer>
{(ctx) => {
return (
<nav className={classes.nav}>
<ul>
{ctx.isLoggedIn && (
<li>
<a href="/">Users</a>
</li>
)}
{ctx.isLoggedIn && (
<li>
<a href="/">Admin</a>
</li>
)}
{ctx.isLoggedIn && (
<li>
<button onClick={props.onLogout}>Logout</button>
</li>
)}
</ul>
</nav>
);
}}
</AuthContext.Consumer>
);
};
export default Navigation;
위와 같이 parameter로 context 데이터를 가져온다. 그리고 이 함수 안에서 JSX 코드를 삽입한다.
그래야지 해당 데이터에 접근할 수 있다.
useContext()
다른 여러 hook들을 불러오는 방식 그대로 사용하면 된다.
import React, { useContext } from "react";
import AuthContext from "../../store/auth-context";
const Navigation = (props) => {
const ctx = useContext(AuthContext) ;
...
}
사용법은 간단하다. React component 함수에서 useContext()
를 호출하면 된다.
그리고 useContext()
에게 사용하려는 context를 가르키는 포인터를 parameter로 전달해준다. (예제에서는 AuthContext
)
그러면 context 값을 얻을 수 있기에 상수에 저장해준다.
그리고 마찬가지로 사용한다. 이 경우가 훨씬 간단하고 쉽다. 그저 변수처럼 사용이 가능하다.
Context Limitaion
그렇다면 모든 것에 React context를 활용할 수 있을까?
우선, context로 component를 대체할 수 없다. component는 재사용이 가능해야한다.
주로 component 또는 전체 앱에서 state 관리를 위해 context를 사용한다. 그럼에도 한계는 존재한다.
예를 들어, 매초 또는 1초에 여러번 state가 변경되는 경우라면 React context는 최적화되어 있지 않다.
이 의견은 실제 React 공식 팀원이 말한 내용이다.
제 개인적인 요약은 새로운 context를 낮은 빈도의 가능성이 낮은 업데이트(locale/theme와 같이)에 사용할 준비가 되었다는 것입니다. 오래된 문맥을 사용했던 것과 같은 방식으로 사용하는 것도 좋습니다. 즉, 정적 값에 대한 다음 구독을 통해 업데이트를 전파합니다. 모든 유동적인 상태 전파를 대체할 준비가 되지 않았습니다.
props의 모든 component comunication을 대체하기 위해 context를 사용해서는 안된다.
props 역시 component를 구성하는 데 있어서 필수적이고 중요하다.
props이 존재함으로써 한 component가 여러 동작을 할 수 있도록, 재사용할 수 있게 만들어준다.
context를 사용할 때 어느 것을 사용해도 무관하지만 useContext()
hook을 사용하는 편이 더 나아보인다.
context가 아무리 좋더라도 state의 잦은 업데이트가 필요한 곳에는 context를 사용하지 말고, 앞의 예시처럼 긴 props chain을 교체하기 위해서 사용해야겠다.