props chain & context
리액트는 화면에 렌더링 할 여러 컴포넌트로 앱을 구성한다. 이 구조를 component tree라고 부르며, 이 때문에 props chaining이 발생한다.
- 예를 들어 F가 로그인 컴포넌트 이고, 컴포넌트 A와 C에서 로그인과 관련된 data를 필요로 한다고 해보자.
- A, C와 F가 공통적으로 공유하고 있는 부모는 App 컴포넌트 뿐이다.
- 따라서 F의 로그인 state를 App까지 끌어 올린 후에, 다시 App에서 그 데이터를 A, C의 props으로 전달해 주어야 할 것이다.
- 어플리케이션의 규모가 커지면, props chaining은 아래 예제처럼 복잡하고 번거로워 질 수 밖에 없다.
// login form
const onSubmitHandler = (e) => {
idElement.current.value = '';
pwElement.current.value = '';
props.onLogin(idState.value, pwState.value);
};
// login
const Login = (props) => {
return (
<aside>
<LoginForm onLogin={props.onLogin} />
</aside>
);
};
// login page
const Render = (props) => {
return (
<section}>
<div>
<Logo />
<Login onLogin={props.onLoginHandler} />
</div>
</section>
);
}
// App
function App() {
const [logInState, setLogInState] = useState(false);
const onLoginHandler = (id, pw) => {
localStorage.setItem('isLoggedIn', '1');
setLogInState(true);
};
// ...
return (
<div>
{logInState && <Main onLogout={onLogoutHandler} />}
{!logInState && <Render onLogin={onLoginHandler} />}
</div>
);
}
이러한 문제를 해결하기 위해, context라는 개념을 사용한다. 리액트는 모든 component가 사용할 수 있는 state storage를 제공하는데(component-wide state), 이 저장소를 통해 길고 복잡한 props chain을 거치지 않아도 컴포넌트끼리 state를 공유할 수 있다.
+) 단, state가 자주(high frequecy, 1초에도 여러번) 바뀌는 경우에는 최적화를 지원하지 않는다.
How to use context?
// context
const AuthContext = React.createContext({
isLoggedIn:false,
onLogin: ()=>{},
onLogout: ()=>{},
});
export default AuthContext;
- src/ context 폴더를 만들고, 폴더 안에서 js 파일을 만든다. ex) src/ context/ auth.js
- createContext(default) | default context를 전달받아서 context 객체를 반환한다.
- default context | 보통 객체 데이터를 전달하며, 이것이 component-wide state가 된다.
- context | 컴포넌트를 포함하는 객체이다. => 변수명을 PascalCase로 만든는 것이 convention이다.
- 이 컨텍스트를 사용할 컴포넌트에게 context를 제공하면(provider), 그 컴포넌트와 자식 컴포넌트 들은 이 context를 소비할 수 있다(consumer). App 컴포넌트에 컨텍스트를 제공하면 모든 컴포넌트에서 해당 컨텍스트를 이용할 수 있다.
provider
// App.js
import AuthContext from '../../context/auth';
const App = () => {
const [isLoggedIn, setIsLoggedIn] = useState(false);
const loginHandler = (email, password) => {
localStorage.setItem('isLoggedIn', '1');
setIsLoggedIn(true);
};
const logoutHandler = () => {
localStorage.setItem('isLoggedIn', '0');
setIsLoggedIn(false);
};
return (
<AuthContext.Provider value={
{ isLoggedIn: isLoggedIn,
onLogin: loginHandler,
onLogout: logoutHandler,
}
}>
<MainHeader />
<main>
{!isLoggedIn && <Login />}
{isLoggedIn && <Home />}
</main>
</AuthContext.Provider>
);
}
- Auth 컨텍스트는 객체 데이터이다. => AuthContext.Provider
- value | context가 전달하는 값
- state가 변경될 때 마다 jsx 코드가 재평가되므로, 이를 이용하면 컨텍스트가 전달하는 값을 동적으로 설정할 수 있다.
- provider 컴포넌트로 감싼 모든 자식 컴포넌트와, 자식의 자식 컴포넌트... 에서, 제공한 context에 접근할 수 있다.
- 더이상 props으로 isLoggedIn, onLogin, onLogout 값을 전달하지 않아도 된다.
consumer
Context.Consumer
import AuthContext from '../../context/auth';
const Navigation = (props) => {
return (
<AuthContext.Consumer>
{(context) => {
if (context.isLoggedIn) {
return (
<nav>
<!-- ... -->
</nav>
);
}
return;
}}
</AuthContext.Consumer>
);
};
export default Navigation;
- consumer는 자식 요소로 콜백함수를 포함해야 한다.
- 콜백 함수는 우리가 설정한 context data를 인자로 받는다.
- 이 때 context. 문법을 이용해서, 기본값으로 hard coding한 data에 접근하려면 provider가 없어야 한다.
- 그러나 일반적으로 데이터는 기본값으로 고정되어 있지 않고 변환되며(isLoggedIn), 이런 데이터에 접근할 때에는 provider와 그의 value 속성이 있어야 한다. => <AuthContext.Provider value={ {isLoggedIn:isLoggedIn} }>
useContext
import React, { useContext } from 'react';
import AuthContext from '../../context/auth';
const Navigation = (props) => {
const context = useContext(AuthContext);
return (
<>
{context.isLoggedIn && (
<nav className={classes.nav}>
<!-- ... -->
</nav>
)}
</>
);
};
- useContext에 context 객체를 전달하면, 우리가 전달한 context value가 변수에 할당된다.
- consemer를 이용하는 것보다 간단하기 때문에 useContext 훅을 주로 사용한다.
refactor
// auth.js
import React, { useState, useEffect } from 'react';
const AuthContext = React.createContext({
isLoggedIn: false,
onLogin: (id, pw) => {},
onLogout: () => {},
});
// 컴포넌트를 반환하는 함수 => hooks 사용 가능
export const AuthContextProvider = (props) => {
const [isLoggedIn, setIsLoggedIn] = useState(false);
useEffect(() => {
if (localStorage.getItem('isLoggedIn') === '1') {
setIsLoggedIn(true);
}
}, []);
const loginHandler = (email, password) => {
localStorage.setItem('isLoggedIn', '1');
setIsLoggedIn(true);
};
const logoutHandler = () => {
localStorage.setItem('isLoggedIn', '0');
setIsLoggedIn(false);
};
return (
<AuthContext.Provider
value={{
isLoggedIn: isLoggedIn,
onLogin: loginHandler,
onLogout: logoutHandler,
}}
>
{props.children}
</AuthContext.Provider>
);
};
export default AuthContext;
// App.js
function App() {
const context = useContext(AuthContext);
return (
<>
<MainHeader />
<main>
{!context.isLoggedIn && <Login />}
{context.isLoggedIn && <Home />}
</main>
</>
);
}
<!-- index.js -->
ReactDOM.render(
<AuthContextProvider>
<App />
</AuthContextProvider>,
document.getElementById('root')
);
'Study > React' 카테고리의 다른 글
etc | Image, Icon, Input (0) | 2022.06.21 |
---|---|
Rules of Hooks (0) | 2022.06.20 |
useReducer (0) | 2022.06.19 |
useEffect (0) | 2022.06.19 |
useRef (0) | 2022.06.18 |