본문 바로가기
SSURENT(풀스택)

[React + TS] SSURENT 프로젝트 초기 구성 + Header

by CromArchive 2025. 11. 19.
반응형

이 프로젝트 개발을 본격적으로 시작하기 위해서 어떤 기술스택을 쓰는게 좋을 지 생각해보았다.

인터넷을 찾아보니 Svelte/SvelteKit, React, Vue3, Solid.js등 다양한 추천들이 있었다.

 

SSURENT에서는 어떤 프론트 기술스택을 사용할까?

 

내가 선정한 기술스택은 Reat + TypeScript 조합이었다.

 

먼저 TS를 선택한 이유는 컴파일 전에 에러를 캐치할 수 있다는 장점이다.

만약 런타임에만 에러를 찾아낼 수 있었다면, 이 에러를 고치는 동안 대여사업 지속의 어려움이 생길 수 있겠다는 생각이 들었다.

TS를 사용한다고 해서 런타임 에러가 아예 발생하지 않는 것은 아니겠지만, 컴파일 시점에서 미리 에러를 캐치해서 몇가지 사항을 예방할 수 있는 것은 큰 이점이라고 생각했다.

 

두번째로 React를 선택한 이유는 방대한 생태계와 모바일 앱으로의 쉬운 전환, 취업 시장에서의 많은 수요이다.

React는 생태계가 방대하기 때문에 학습에 있어 쉽게 자료를 찾을 수 있고, React Native가 있기 때문에 모바일 앱으로의 전환 시에 보다 쉽게 코드를 재사용할 수 있다는 장점이 있다. 또한 최근 프론트엔드 개발 시장에서 React가 차지하는 비중또한 크기 때문에 나의 추후 커리어에서 도움이 될 것이라고 생각했다.

 


 

기술 스택 선정 이후에 깃허브도 만들고 피그마에서 디자인도 만들어서 본격적인 프론트엔드 개발에 들어갔다.

초반에는 디자인패턴에 대한 이해도가 없어서 막 작성하다가 디자인 패턴에 대해 알게 되었다.

다행히 정말 초반 단계라서 만들어놓은 디렉토리와 파일들이 적어서 금방 리팩터링 할 수 있었다.

내가 선택한 디자인 패턴은 Feature-Based Architecture이다. 

이 디자인 패턴을 선택한 이유는 4가지이다.

 

1. 높은 응집도와 낮은 결합도 : 관련된 코드가 한 곳에 모여 있어 기능 개발과 유지보수가 효율적이다. 각 기능이 독립적으로 존재하여 다른 기능에 미치는 영향을 최소화할 수 있다.

2. 확장성과 모듈성 : 새로운 기능을 추가할 때 새로운 feature 폴더를 생성하면 되므로 확장이 용이하다. 각 기능을 독립적인 패키지로 분리하기도 쉽다.

3. 팀 협업 최적화 : 각 팀이나 개발자가 특정 feature를 담당할 수 있어 병렬 작업이 수월하고, 코드 충돌이 줄어든다.

4. 도메인 주도 설계(DDD) 친화적 : 비즈니스 도메인과 코드 구조가 일치하여 비즈니스 로직을 이해하기 쉽다.

출처 : https://jackerlab.com/feature-vs-layer-project-structure/

 

 

메인화면 디자인하기

전체적으로 UI구성은 피그마로 진행했다. 

아래 이미지는 메인 화면에 대한 디자인 이미지이다.

메인 화면에 대한 구성은

페이지 이동 헤더 + 대여 / 반납 바로가기 버튼 + 대여사업 소개를 위한 요소들로 구성하였다.

추가로 난 디자인을 잘 못하기 때문에 CSS 라이브러리로 BootStrap을 이용하기로 했다.

 

Header 기능 구현하기

상단에 위치한 헤더는 메인 페이지 말고도 다른 페이지에서도 보여야 하는 컴포넌트이다.

따라서 share 패키지로 넣어주었다.

또한 로그인+회원가입 페이지에서는 로그인 버튼을 보이지 않게 하려고 했다.

 

원래는 모바일에서도 초기 디자인 한 화면처럼 길게 헤더를 구성하려 하였으나, 실제로 적용해봤을 때 누르기 힘들다는 점을 확인할 수 있었다.

그래서 다른 버튼(대여하기, 반납하기, 정보, 마이페이지, 로그인)은 햄버거 버튼으로 숨겨놓는것이 UX적으로 좋아보여서 수정하였다.

 

아래 두 상수는 현재 경로를 추적하기 위한 location과 모바일 메뉴의 열람정도를 판단하는 isOpen이다.

const location = useLocation();
const [isOpen, setIsOpen] = useState(false);
const hideLoginButton = location.pathname === '/login' || location.pathname === '/changePW';

 

hideLoginButton은 location이 로그인이나 비밀번호 변경 페이지일 때, 로그인 버튼을 숨기기 위한 변수이다.

 

const linkClass = ({ isActive }: { isActive: boolean }) =>
        "nav-link px-3 link-dark fw-bold" + (isActive ? " active" : "");

 

이 코드는 현재 사용자가 있는 페이지가 어디인지 알려주기 위한 코드로 isActive가 True일 때 화면에 어떤 페이지인지 표시하기 위해 사용했다.

메인화면에서는 딱히 쓸 일은 없고, 다른 페이지로 넘어갔을 때 텍스트로 표기하는 역할이다. (추후에 삭제할 수도)

이런식으로 나오게 된다

 

아래 코드는 모바일 화면에서 햄버거 버튼을 제어하는 역할이다.

const toggleNavbar = () => setIsOpen(!isOpen);
const closeNavbar = () => setIsOpen(false);

초기값을 false로 해서 모바일로 들어왔을 때 햄버거 버튼이 열려있는 상태로 맞이하지 않도록 설정해주었다

햄버거 버튼 열었을 때

 

로고 영역 구현하기

<NavLink
    to="/"
    className="d-flex align-items-center text-dark text-decoration-none"
    onClick={closeNavbar}
>
    <img src={logo} alt="로고 이미지" style={{width: '90px'}} className="img-fluid me-2"/>
    <span className="fs-4">SSURENT</span>
</NavLink>

로고 영역은 위처럼 네비게이션 바 영역으로 설정했고, 눌렀을 때 홈 화면인 "/"으로 이동하도록 설정해주었다.

d-flex align-items-center로 로고와 텍스트를 가로로 나란히 배치해주었고,

text-decoration-none으로 링크 밑줄도 제거해주었다.

 

햄버거 버튼 구현하기

<button
    className="navbar-toggler"
    type="button"
    onClick={toggleNavbar}
    aria-controls="navbarNav"
    aria-expanded={isOpen}
    aria-label="Toggle navigation"
>
    <span className="navbar-toggler-icon"></span>
</button>

<div className={`collapse navbar-collapse ${isOpen ? 'show' : ''}`} id="navbarNav">

버튼 타입으로 설정하고

onClick={toggleNavbar}으로 클릭하면 메뉴 토글을 열 수 있도록 설정했다.

aria-expanded={isOpen} 으로 열려있는 상태인지 확인하고 메뉴를 여닫는 역할이다.

navbar-toggler을 클래스명으로 지정함으로서 bootstrap에서 제공하는 햄버거버튼 css스타일로 적용할 수 있다.

가로가 991px이하인 경우 햄버거 버튼으로 활성화 해준다.

아래 collapse navbar-collapse 부분으로 isOpen이 true일 때만 토글을 활성화시키도록 설정해주었다.

 

나머지 네비게이션 메뉴 구현하기

<ul className="navbar-nav me-auto ms-lg-5 mb-3 mb-lg-0">
                    <li className="nav-item">
                        <NavLink to="/rent" className={linkClass} onClick={closeNavbar}>
                            대여하기
                        </NavLink>
                    </li>
                    <li className="nav-item">
                        <NavLink to="/return" className={linkClass} onClick={closeNavbar}>
                            반납하기
                        </NavLink>
                    </li>
                    <li className="nav-item">
                        <NavLink to="/info" className={linkClass} onClick={closeNavbar}>
                            정보
                        </NavLink>
                    </li>
                    <li className="nav-item">
                        <NavLink to="/mypage" className={linkClass} onClick={closeNavbar}>
                            마이페이지
                        </NavLink>
                    </li>
                </ul>

                    {!hideLoginButton && (
                        <div className="text-center text-lg-end mt-2 mt-lg-0">
                            <NavLink to="/login" onClick={closeNavbar}>
                                <button type="button" className="btn btn-outline-primary w-100" style={{maxWidth: '200px'}}>
                                    로그인
                                </button>
                            </NavLink>
                        </div>
                    )}

각 항목들을 클릭하면 해당 페이지로 이동하고 모바일 메뉴라면 자동으로 닫히도록 구현했다.

로그인 버튼은 초기 구상한 것처럼 hide조건일 때 보이지 않게 구현했다.

 

 

전체 코드

아래는 내가 구성한 전체 코드이다.

import { NavLink, useLocation } from "react-router-dom";
import logo from "../../assets/images/ssulogo.jpg";
import "../../styles/App.css";

import {useState} from "react";

export default function Header() {
    const location = useLocation();
    const hideLoginButton = location.pathname === '/login' || location.pathname === '/changePW';
    const [isOpen, setIsOpen] = useState(false);
    const linkClass = ({ isActive }: { isActive: boolean }) =>
        "nav-link px-3 link-dark fw-bold" + (isActive ? " active" : "");

    const toggleNavbar = () => setIsOpen(!isOpen);
    const closeNavbar = () => setIsOpen(false);

    return (
        <div className="container">
            <nav className="navbar navbar-expand-lg py-3 mb-4 border-bottom">
                <div className="container-fluid">
                <NavLink
                    to="/"
                    className="d-flex align-items-center text-dark text-decoration-none"
                    onClick={closeNavbar}
                >
                    <img src={logo} alt="로고 이미지" style={{width: '90px'}} className="img-fluid me-2"/>
                    <span className="fs-4">SSURENT</span>
                </NavLink>
                    {/* 햄버거 버튼 (모바일에서만 보임)*/}
                    <button
                        className="navbar-toggler"
                        type="button"
                        onClick={toggleNavbar}
                        aria-controls="navbarNav"
                        aria-expanded={isOpen}
                        aria-label="Toggle navigation"
                    >
                        <span className="navbar-toggler-icon"></span>
                    </button>


                <div className={`collapse navbar-collapse ${isOpen ? 'show' : ''}`} id="navbarNav">
                <ul className="navbar-nav me-auto ms-lg-5 mb-3 mb-lg-0">
                    <li className="nav-item">
                        <NavLink to="/rent" className={linkClass} onClick={closeNavbar}>
                            대여하기
                        </NavLink>
                    </li>
                    <li className="nav-item">
                        <NavLink to="/return" className={linkClass} onClick={closeNavbar}>
                            반납하기
                        </NavLink>
                    </li>
                    <li className="nav-item">
                        <NavLink to="/info" className={linkClass} onClick={closeNavbar}>
                            정보
                        </NavLink>
                    </li>
                    <li className="nav-item">
                        <NavLink to="/mypage" className={linkClass} onClick={closeNavbar}>
                            마이페이지
                        </NavLink>
                    </li>
                </ul>

                    {!hideLoginButton && (
                        <div className="text-center text-lg-end mt-2 mt-lg-0">
                            <NavLink to="/login" onClick={closeNavbar}>
                                <button type="button" className="btn btn-outline-primary w-100" style={{maxWidth: '200px'}}>
                                    로그인
                                </button>
                            </NavLink>
                        </div>
                    )}
                    </div>
                </div>
            </nav>
        </div>
    );
}

 

 

728x90
반응형

'SSURENT(풀스택)' 카테고리의 다른 글

[React + TS] 메인화면 구성하기 + Carousel  (0) 2025.11.19
SSURENT 프로젝트 기획하기  (0) 2025.11.18