아코디언 메뉴가 꺼졌다 켜질때마다 그에 대응하여 status bar과 메인 section부분의 사이즈가 바뀌도록 하려고 했다.
아코디언 메뉴와 status bar의 경우 같은 부모를 두고 있어 state를 공유할 수 있기에 구현이 수월했지만..메인 section의 경우 아코디언메뉴와 연결되지 않아 state를 공유할 수 없었다...!
하지만 너무너무 내비게이션 메뉴 on/off기능을 구현하고 싶었기에, 상태관리 라이브러리를 사용하고자 하였고, 이왕이면 React에서 제공해주는 기능으로 사용하자는 의견이 들어와 Context API
를 사용하여 구현해보았다.
Context API는 React에서 제공하는 상태관리 API이다.
한 곳에서 생성한 state가 이곳 저곳에서 사용해야되는 경우가 잦은데, 이럴때마다 props로 state를 여러 단계 넘겨줘야되다보니 불필요한 component가 생성되는 경우도 있다.
편하게 사용하려고 만든 state지만, 꼭 연결지어 사용해야만 하다보니 디렉토리의 depth를 높여 오히려 component연결고리가 복잡해지는 불상사가 생기는 것이다.
이런 상황을 대응하기위해 Redux
, mobx
같은 상태(status)관리 라이브러리가 탄생하였다.
많은 개발자들이 불편함을 느끼고 있었기에, Redux
는 엄청난 흥행을 하였고, facebook에서도 state 관리의 필요성을 느끼게 되어 React
환경에서 가장 최적화된 상태관리 기능을 구현하였다.
그 구현물이 바로 Context API
이다.
다른 상태관리 라이브러리와 비슷하게 component가 연결되어있지 않아도 이 API를 통해 state를 이곳 저곳에서 연결할 수 있다.
그럼 Context API를 사용해서 side menu를 움직일 때마다 화면이 대응하도록 해보자.
Directory
해당 프로젝트의 디렉토리는 아래와 같이 구성되어있다.
Routes
|-- NavBar
|-- AccordianBar
|-- StatusBar
|-- TAMS
|-- Jupiter
Routes component 아래에 NavBar, TAMS, Jupiter가 있는 상태이며, accordian bar의 on/off state는 "accordian bar, status bar, TAMS, Jupiter"에 전달되어야 한다.
store*의 저장소인 Provider(<Context.Provider>
)를 만들기 위해 Context를 생성한다
store : 상태관리를 하는 전용 장소 - class같이 한 곳에 묶어둔 다음에 호출해서 사용한다.
import { createContext } from 'react';
// createContext는 우리가 관리할 state가 어떻게 구성되는지 설정하는 곳이다.
const OpenContext = createContext({
isNavOpened: true,
controlNav: () => {},
});
export default OpenContext;
state를 모아둔 것을 Provider(store)라 생각하면 된다.
<StateProvider>
가 하는일OpenContext.Provider
를 return한다.import React, { useState } from 'react';
import OpenContext from '../../context/Open.context';
// 사용하고자 하는 컴포넌트 최상위에 지정할 Provider컴포넌트 입니다.
const StatusProvider = ({ children }) => {
// 아코디언 메뉴바를 on/off 시킬 함수
// 클릭 때마다 boolean 값을 변경시켜줄 것이기 때문에
// 기존 state에 담고 있던 값에 반하는 값을 저장하도록 했다.
const controlNav = () => {
setNavOpened(prev => {
return { ...prev, isNavOpened: !prev.isNavOpened };
});
};
// state 초기화 객체
const initialState = {
isNavOpened: true,
controlNav,
};
// useState를 사용하여 state 뭉치 선언
const [isNavOpened, setNavOpened] = useState(initialState);
// StatusProvider에 state를 사용할 컴포넌트들을 호출하려면 children을 갖고 있어야 한다.
// 그래서 마지막 return에서 {children}을 꼭! 리턴해야 한다.
return (
<OpenContext.Provider value={isNavOpened}>{children}</OpenContext.Provider>
);
};
export default StatusProvider;
Provider를 사용하고자 하는 자식 컴포넌트들의 가장 최상단에서 <Provider>
로 감싸준다.
이 프로젝트의 경우 Routes.js
가 가장 최상단이여서 Routes.js
를 StatusProvider
로 감싸주었다.
import React, { Component } from 'react';
export default class Routes extends Component {
render() {
return (
<StatusProvider>
<Router>
<NavBar />
<Switch>
<PrivateRoutes exact path="/" component={Main} />
<PublicRoutes exact path="/signUp" component={SignUp} />
<PublicRoutes exact path="/signIn" component={SignIn} />
<PrivateRoutes exact path="/jupiter" component={Jupiter} />
<PrivateRoutes exact path="/tams" component={Tams} />
</Switch>
</Router>
</StatusProvider>
);
}
}
state를 호출하고 싶은 곳에 가서 context를 호출해주면 된다.
import React, { useState, useEffect, useContext } from 'react';
import { BsList } from 'react-icons/bs';
import styled from 'styled-components';
import OpenContext from '../../context/Open.context';
const StatusBar = () => {
// context api 호출
const { controlNav } = useContext(OpenContext);
const value = useContext(OpenContext);
return (
<BarWrapper className={value.isNavOpened ? '' : 'expand'}>
<IconWrapper onClick={controlNav}>
<BsList />
</IconWrapper>
{isLogin ? (
<StatusWrapper>
<TitleWrapper>
안녕하세요, <span>Admin</span>님
</TitleWrapper>
<Button type="button" onClick={handleLogOut}>
Log Out
</Button>
</StatusWrapper>
) : (
<StatusWrapper>
<TitleWrapper>로그인을 진행해주세요</TitleWrapper>
<Button type="button" onClick={() => history.push('/signIn')}>
Log In
</Button>
</StatusWrapper>
)}
</BarWrapper>
);
};
export default StatusBar;