Context API를 사용해 Side Menu 움직여보기

김현재·2021년 10월 21일
0

기업협업 project

목록 보기
6/8

아코디언 메뉴가 꺼졌다 켜질때마다 그에 대응하여 status bar과 메인 section부분의 사이즈가 바뀌도록 하려고 했다.
아코디언 메뉴와 status bar의 경우 같은 부모를 두고 있어 state를 공유할 수 있기에 구현이 수월했지만..메인 section의 경우 아코디언메뉴와 연결되지 않아 state를 공유할 수 없었다...!

하지만 너무너무 내비게이션 메뉴 on/off기능을 구현하고 싶었기에, 상태관리 라이브러리를 사용하고자 하였고, 이왕이면 React에서 제공해주는 기능으로 사용하자는 의견이 들어와 Context API 를 사용하여 구현해보았다.

Context API란?

Context API는 React에서 제공하는 상태관리 API이다.
한 곳에서 생성한 state가 이곳 저곳에서 사용해야되는 경우가 잦은데, 이럴때마다 props로 state를 여러 단계 넘겨줘야되다보니 불필요한 component가 생성되는 경우도 있다.
편하게 사용하려고 만든 state지만, 꼭 연결지어 사용해야만 하다보니 디렉토리의 depth를 높여 오히려 component연결고리가 복잡해지는 불상사가 생기는 것이다.

이런 상황을 대응하기위해 Redux, mobx 같은 상태(status)관리 라이브러리가 탄생하였다.
많은 개발자들이 불편함을 느끼고 있었기에, Redux는 엄청난 흥행을 하였고, facebook에서도 state 관리의 필요성을 느끼게 되어 React환경에서 가장 최적화된 상태관리 기능을 구현하였다.
그 구현물이 바로 Context API이다.

Context API 사용의 장점

다른 상태관리 라이브러리와 비슷하게 component가 연결되어있지 않아도 이 API를 통해 state를 이곳 저곳에서 연결할 수 있다.

Context API 사용해보기

그럼 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"에 전달되어야 한다.

1. createContext 생성

store*의 저장소인 Provider(<Context.Provider>)를 만들기 위해 Context를 생성한다

store : 상태관리를 하는 전용 장소 - class같이 한 곳에 묶어둔 다음에 호출해서 사용한다.

import { createContext } from 'react';

// createContext는 우리가 관리할 state가 어떻게 구성되는지 설정하는 곳이다.
const OpenContext = createContext({
  isNavOpened: true,
  controlNav: () => {},
});

export default OpenContext;

2. Provider 생성

state를 모아둔 것을 Provider(store)라 생각하면 된다.

export 되는 <StateProvider>가 하는일

  1. 하위에서 사용 할 state와 setState를 정의한다.
  2. state를 관리할 store를 만들기 위해 creactContext 를 받아와서 OpenContext.Provider 를 return한다.
    => 최상단 context로 관리할 최상단 component를 StateProvider로 감쌀 것이고, 그러면 StateProvider 아래에 있는 모든 component에 OpenContext가 적용된다.
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;

3. Provider 위치 지정하기

Provider를 사용하고자 하는 자식 컴포넌트들의 가장 최상단에서 <Provider>로 감싸준다.
이 프로젝트의 경우 Routes.js가 가장 최상단이여서 Routes.jsStatusProvider로 감싸주었다.

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>
    );
  }
}

4. 원하는 자식요소에서 context 호출하기

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;

결과물



참고자료

profile
쉽게만 살아가면 재미없어 빙고!

0개의 댓글