React 내부적으로 state를 관리할 수 있도록 도움
props 체인을 구축하지 않고도, 앱의 어떤 컴포넌트에서라도 직접 변경하여 직접 전달할 수 있게 해준다.
리액트에 내장된 State 저장소인 React Context
를 사용하면 긴 props 체인 만들지 않고도 컴포넌트 전체 state에 관해서 액션을 트리거할 수 있다.
context를 사용하면 중간에 있는 엘리먼트들에게 props를 넘겨주지 않아도 된다.
전역적으로 사용되는 값을 관리할 수 있다. (함수, 상태, 라이브러리 인스턴스 등)
Redux는 '상태(state)'를 관리,
/ Context API는 React 컴포넌트들끼리 props로 정보를 주고 받지 않고 전역적으로 데이터를 가져다 쓸 수 있게 만든 툴
const MyStore = React.createContext(defaultValue);
// 예제
// ./src/store/auth-context.js
const AuthContext = React.createContext({
isLoggdIn: false,
});
export default AuthContext;
// ./src/App.js
const [isLoggedIn, setIsLoggedIn] = useState(false);
return (
<AuthContext.Provider>
<MainHeader isAuthenticated={isLoggedIn} onLogout={logoutHandler} />
<main>
{!isLoggedIn && <Login onLogin={loginHandler} />}
{isLoggedIn && <Home onLogout={logoutHandler} />}
</main>
</AuthContext.Provider>
);
/* 2-1. 사용 예제
./src/componenets/Header/Navigation.js */
const Navigation = (props) => {
return (
<AuthContext.Consumer>
{(ctx) => {
return (
<nav className={classes.nav}>
<ul>
{ctx.isLoggedIn && (
<li>
<a href="/">Users</a>
</li>
)}
{props.isLoggedIn && (
<li>
<a href="/">Admin</a>
</li>
)}
{props.isLoggedIn && (
<li>
<button onClick={props.onLogout}>Logout</button>
</li>
)}
</ul>
</nav>
);
}}
</AuthContext.Consumer>
);
};
Context.Consumer
로 감싸준다.{ isLoggdIn: false, }
가 된다.🧐 근데 저장하면 TypeError가 뜬다. 왜?
왜냐면 React.createContext
에 기본값이 있지만, 이는 실제로 공급자 없이 소비하는 경우에만 사용되기 때문이다. 따라서 공급자는 필요하지 않음.
🧐 근데 공급자가 필요하다고 했잖아?
정확히 말하면 기본값이 있으면 공급자는 필요 없다.
그러나 실제론 변할 수 있는 값을 갖기 위해 공급자를 사용할 것이다.
따라서 충돌이 일어나지 않기 위해선 공급자 컴포넌트에서 프롭 값을 주자.
// ./src/App.js
return (
<AuthContext.Provider value={{ isLoggdIn: false }}>
<MainHeader isAuthenticated={isLoggedIn} onLogout={logoutHandler} />
<main>
{!isLoggedIn && <Login onLogin={loginHandler} />}
{isLoggedIn && <Home onLogout={logoutHandler} />}
</main>
</AuthContext.Provider>
);
value
라는 이름을 가져야한다.이제 문제없이 로드된다.
근데 링크가 누락됨
→ 왜냐면 ctx.isLoggdIn
에서 데이터를 가져오기 때문.
// ./src/App.js
return (
<AuthContext.Provider
value={{ isLoggedIn: isLoggedIn }}>
// false에서 isLoggdIn으로 변경
<MainHeader isAuthenticated={isLoggedIn} onLogout={logoutHandler} />
<main>
{!isLoggedIn && <Login onLogin={loginHandler} />}
{isLoggedIn && <Home onLogout={logoutHandler} />}
</main>
</AuthContext.Provider>
);
isLoggedIn
값을 하드코딩하는 대신, isLoggedIn
변경한다.value
객체는 isLoggedIn
변경될 때마다 리액트에 의해 업데이트 된다.그래서 isAuthenticated
의 props 제거할 수 있다.
// ./src/App.js
return (
<AuthContext.Provider value={{ isLoggedIn: isLoggedIn }}>
<MainHeader onLogout={logoutHandler} />
<main>
{!isLoggedIn && <Login onLogin={loginHandler} />}
{isLoggedIn && <Home onLogout={logoutHandler} />}
</main>
</AuthContext.Provider>
);
그리고 MainHeader에선 Navigation으로의 전달을 중단한다.
const MainHeader = (props) => {
return (
<header className={classes['main-header']}>
<h1>A Typical Page</h1>
<Navigation
// isLoggedIn={props.isAuthenticated}
onLogout={props.onLogout} />
</header>
);
};
이제 Navigation
에서 context.isLoggedIn
을 사용할 수 있다.
모든 props를 ctx로 바꿔주자.
// ./src/componenets/Header/Navigation.js
const Navigation = () => {
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={ctx.onLogout}>Logout</button>
</li>
)}
</ul>
</nav>
);
}}
</AuthContext.Consumer>
);
};
다시 링크도 돌아왔다.
근데 함수에서 코드를 반환({(ctx) => { return (....)}}
) 하는 건 별로라서 useContext hook을
사용하는 게 낫다.
// ./src/componenets/Header/Navigation.js
import { useContext } from 'react'; // 추가
const Navigation = (props) => {
const ctx = useContext(AuthContext); // 추가
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={ctx.onLogout}>Logout</button>
</li>
)}
</ul>
</nav>
);
};
이렇게 하면 Context 값을 얻을 수 있다.
// ./src/store/auth-context.js
const AuthContext = React.createContext({
isLoggdIn: false,
onLogout: () => {}
});
export default AuthContext;
// ./src/store/auth-context.js
const AuthContext = React.createContext({
isLoggdIn: false,
onLogout: () => {}
});
// 추가
export const AuthContextProvider = (props) => {
return <AuthContext.Provider>{props.children}</AuthContext.Provider>;
};
export default AuthContext;
AuthContextProvider
컴포넌트를 추가해 props 받아와서 들어오는 모든 것을 전달하면 된다.
이렇게 하면 이제 useState를 임포트 할 수 있다.
// ./src/store/auth-context.js
import React, { useState } from "react";
const AuthContext = React.createContext({
isLoggdIn: false,
onLogout: () => {},
});
export const AuthContextProvider = (props) => {
const [isLoggdIn, setIsLoggedIn] = useState(false);
const logoutHandler = () => {
setIsLoggedIn(false);
};
const loginHandler = () => {
setIsLoggedIn(true);
};
return (
<AuthContext.Provider
value={{
isLoggdIn: isLoggdIn,
onLogout: logoutHandler,
onLogin: (email, password) => {},
}}
>
{props.children}
</AuthContext.Provider>
);
};
AuthContextProvider
컴포넌트에서 전체 로그인 State를 관리하여 모든 컨텍스트를 설정했다. === App 컴포넌트 간결// ./src/App.js
function App() {
const ctx = useContext(AuthContext);
return (
<React.Fragment>
<MainHeader />
<main>
{!ctx.isLoggdIn && <Login />}
{ctx.isLoggdIn && <Home />}
</main>
</React.Fragment>
);
}
// ./src/store/auth-context.js
import React, { useState, useEffect } from "react";
const AuthContext = React.createContext({
isLoggdIn: false,
onLogout: () => {},
onLogin: (email, password) => {},
});
export const AuthContextProvider = (props) => {
const [isLoggdIn, setIsLoggedIn] = useState(false);
useEffect(() => {
const storedUserLoggedInInformation = localStorage.getItem("isLoggedIn");
if (storedUserLoggedInInformation === "1") {
setIsLoggedIn(true);
}
}, []);
const logoutHandler = () => {
localStorage.removeItem("isLoggedIn");
setIsLoggedIn(false);
};
const loginHandler = () => {
localStorage.setItem("isLoggedIn", "1");
setIsLoggedIn(true);
};
return (
<AuthContext.Provider
value={{
isLoggdIn: isLoggdIn,
onLogout: logoutHandler,
onLogin: loginHandler,
}}
>
{props.children}
</AuthContext.Provider>
);
};
export default AuthContext;