3. 헤더 컴포넌트와 로그인 유지

이번에는 헤더 컴포넌트를 구현하고, 로그인 후에는 새로고침해도 로그인이 유지되는 기능을 구현하겠습니다.

헤더 컴포넌트는 프로젝트 레이아웃의 기반에 해당되는 컴포넌트이고, 추후 포스트 페이지와 포스트 목록 페이지에서 사용되기 때문에, base 라는 이름으로 분류하여 컴포넌트를 만들어주겠습니다.

일단 헤더 컴포넌트를 만들기 전에, 반응형 디자인을 할 때 더 쉽게 해주기 위하여 Responsive 라는 컴포넌트를 작성해주겠습니다.

components/base/Responsive.js

import React from 'react';
import './Responsive.scss';

const Responsive = ({ children, className }) => {
  return <div className={`Responsive ${className || ''}`}>{children}</div>;
};

export default Responsive;

components/base/Responsive.scss

.Responsive {
  padding-left: 1rem;
  padding-right: 1rem;
  width: 1024px;
  margin: 0 auto;
  @include media('<desktop') {
    width: 768px;
  }
  @include media('<tablet') {
    width: 100%;
  }
}

그 다음에는 Header 컴포넌트를 만드세요.

components/base/Header.js

import React from 'react';
import { Link } from 'react-router-dom';
import './Header.scss';
import Responsive from './Responsive';

const Header = ({ user, onLogout }) => {
  return (
    <Responsive>
      <div className="Header">
        <Link to="/" className="logo">
          REACTERS
        </Link>
        <div className="right-area">
          {user ? (
            <div className="logged-in">
              <div className="username">{user.username}</div>
              <button onClick={onLogout}>로그아웃</button>
            </div>
          ) : (
            <Link to="/login">로그인</Link>
          )}
        </div>
      </div>
    </Responsive>
  );
};

export default Header;

components/base/Header.scss

.Header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-top: 1rem;
  padding-bottom: 1rem;
  margin-bottom: 2rem;
  .logo {
    font-weight: 800;
    letter-spacing: 2px;
  }
  .right-area {
    .logged-in {
      display: flex;
      align-items: center;
      .username {
        font-size: 0.875rem;
        font-weight: 600;
        color: $oc-gray-9;
        margin-right: 0.5rem;
      }
    }
    a,
    button {
      display: flex;
      align-items: center;
      cursor: pointer;
      border: 1px solid $oc-gray-8;
      height: 2rem;
      padding-left: 0.75rem;
      padding-right: 0.75rem;
      font-size: 0.875rem;
      border-radius: 1rem;
      font-weight: 600;
      color: $oc-gray-8;
      &:hover {
        background: $oc-gray-8;
        color: white;
      }
    }
  }
}

이제 이 컴포넌트를 PostListPage 에서 렌더링 해봅시다.

pages/PostListPage.js

import React from 'react';
import './PostListPage.scss';
import Header from '../components/base/Header';

/**
 * 여러 포스트 목록을 보여주는 페이지
 */
const PostListPage = () => {
  return (
    <>
      <Header />
      <div className="PostListPage" />
    </>
  );
};

export default PostListPage;

헤더가 잘 보여지고 있나요?

이제 헤더를 위한 컨테이너 컴포넌트를 만들고 리덕스에서 user 값을 조회해서 props 로 사용하겠습니다.

containers/base/HeaderContainer.js

import React, { Component } from 'react';
import { connect } from 'react-redux';
import Header from '../../components/base/Header';

class HeaderContainer extends Component {
  handleLogout = () => {
    localStorage.removeItem('user');
    localStorage.removeItem('authorization');
    window.location.reload();
  };
  render() {
    return <Header user={this.props.user} onLogout={this.handleLogout} />;
  }
}

export default connect(state => ({
  user: state.user.user
}))(HeaderContainer);

그리고 PostListPage 에서 기존의 Header 를 HeaderContainer 로 대체시키세요.

pages/PostListPage.js

import React from 'react';
import './PostListPage.scss';
import HeaderContainer from '../containers/base/HeaderContainer';

/**
 * 여러 포스트 목록을 보여주는 페이지
 */
const PostListPage = () => {
  return (
    <>
      <HeaderContainer />
      <div className="PostListPage" />
    </>
  );
};

export default PostListPage;

이제 만약 로그인을 하고 나면 다음과 같이 현재 계정명과 로그아웃 버튼이 보여질 것입니다.

하지만 새로고침하면 상태는 날라가게 됩니다.

우리가 이전에 회원가입 혹은 로그인 후에 authorization 객체를 localStorage 에 저장했었는데요, 이를 불러와서 사용하면 됩니다.

우리는 결국 프로젝트가 브라우저상에 가장 처음 렌더링 됐을 때 로그인 정보를 재사용하는 로직을 구현해야 하는 것 인데요, 가장 간단한 방법은 App.js 에서의 componentDidMount 를 사용하는 것 입니다. 하지만, App 컴포넌트는 현재 최상위 컴포넌트인데 이 컴포넌트에 connect 를 사용하는것은 별로 권장되지 않습니다.

그 대신에 렌더링 하는 내용은 없지만, 프로젝트의 componentDidMount 부분만 담당하는 Core 라는 컨테이너 컴포넌트를 사용하면 꽤나 편합니다.

containers/base/Core.js

import { Component } from 'react';
import { connect } from 'react-redux';
import { check, tempSetUser } from '../../modules/user';
import { setToken } from '../../lib/api/client';

class Core extends Component {
  initialize = async () => {
    // 로그인 설정 불러오기
    const user = localStorage.getItem('user');
    const authorization = localStorage.getItem('authorization');
    if (!user) return;
    if (!authorization) return;
    this.props.tempSetUser(JSON.parse(user)); // check 이 완료 될 때 까지 임시로 유저정보 보여줌
    const parsedAuth = JSON.parse(authorization);
    setToken(`Bearer ${parsedAuth.access_token}`);
    try {
      await this.props.check();
    } catch (e) {
      localStorage.removeItem('user');
      localStorage.removeItem('authorization');
    }
  };
  componentDidMount() {
    this.initialize();
  }

  render() {
    return null;
  }
}

export default connect(
  () => ({}),
  { check, tempSetUser }
)(Core);

그리고 이 컴포넌트는 App 컴포넌트 가장 아래에 렌더링하세요.

App.js

import React from 'react';
import { Route } from 'react-router-dom';
import PostListPage from './pages/PostListPage';
import LoginPage from './pages/LoginPage';
import RegisterPage from './pages/RegisterPage';
import WritePage from './pages/WritePage';
import PostPage from './pages/PostPage';
import Core from './containers/base/Core';
const App = () => {
  return (
    <>
      <Route component={PostListPage} path="/" exact />
      <Route component={LoginPage} path="/login" />
      <Route component={RegisterPage} path="/register" />
      <Route component={WritePage} path="/write" />
      <Route component={PostPage} path="/posts/:postId" />
      <Core />
    </>
  );
};

export default App;

이제 페이지를 새로고침 해보세요. 내용이 유지되어 있나요?

results matching ""

    No results matching ""