안녕하세요. 오늘은 기존에 만들어 놨던 포토폴리오를 리액트로 마이그레이션 하는 작업중 첫번째 글입니다.
제가 이 작업을 하는 이유에 대해 먼저 말씀 드리겠습니다.
저는 비전공자로 개발을 시작하면서 처음 6개월은 학원에서 퍼블리셔 과정을 학습하였습니다.
해당 과정 중 배운것은 html,css,js,node.js,mongodb 등으로 학습한 내용을 바탕으로
mpa (multi page application) 를 만들어 포토폴리오로 사용하였습니다.
해당 과정 후 프론트엔드 개발자로 취업하였고 회사가 백엔드 기반인지라 1개월 후 백엔드 개발자로 전향하였는데요
8개월차가 된 지금 포토폴리오 구조를 바꾸려는 이유는 다음과 같습니다.
1. 대한민국 == 자바
표준 전자 정부 프레임워크는 자바입니다. 앞으로도 백엔드단은 자바를 알아야 유지 보수 측면에서 좋을것이다.
스프링부트 공부를 주로 하였고 api 만드는 게 익숙해짐
기본 웹사이트 구조 backend api를 구현 중이다.
2. 백엔드가 확실히 정해졌으니 프론트도 확실히 정해야한다.
일단 지금 포토폴리오는 node.js와 ejs 템플릿 엔진을 사용하였으니 바꿔야함
그렇다면 무엇으로 바꿔야하나....
자바스크립트를 좀 알고 있으니 자바스크립트 기반의 프레임워크 중 리액트 아니면 뷰인데
개인적으로 뷰는 불호라 리액트 선정(사실 리액트도 좀... 대안이 별로 없네요)
위와 같은 과정에 의해서 리액트변환 작업을 하는 것입니다.
그러면 바로 시작해 보겠습니다.
구조는 제가 리액트 프로젝트 작성할때와 동일합니다.
페이지는 route를 사용하고 상태 관리는 recoil을 사용합니다.
https://github.com/muganghskim/stdfront
깃헙 주소입니다.
import React from "react";
import { useRecoilState } from "recoil";
import "../assets/css/header.css";
import {
depthHeight,
hbgMenuOpen,
openMenuItem
} from "../recoil/atoms/dropdown";
const Header: React.FC = () => {
const [height, setHeight] = useRecoilState(depthHeight);
const [isOpenHbgMenu, setIsOpenHbgMenu] = useRecoilState(hbgMenuOpen);
const [openItem, setOpenItem] = useRecoilState(openMenuItem);
const menuData = [
{
name: "Ai 갤러리1",
link: "/story",
subMenu: [
{ name: "아트스토리", link: "/story" },
{ name: "주요작품", link: "/frame" },
{ name: "사회적기부", link: "/donaition" }
]
},
{
name: "Ai 갤러리2",
link: "/story",
subMenu: [
{ name: "아트스토리", link: "/story" },
{ name: "주요작품", link: "/frame" },
{ name: "사회적기부", link: "/donaition" }
]
},
{
name: "Ai 갤러리3",
link: "/story",
subMenu: [
{ name: "아트스토리", link: "/story" },
{ name: "주요작품", link: "/frame" },
{ name: "사회적기부", link: "/donaition" }
]
},
{
name: "Ai 갤러리4",
link: "/story",
subMenu: [
{ name: "아트스토리", link: "/story" },
{ name: "주요작품", link: "/frame" },
{ name: "사회적기부", link: "/donaition" }
]
}
];
return (
<>
<div id="header">
<div className="center">
<h1 className="logo">
<a href="/">
<img src={process.env.PUBLIC_URL + "/img/logo.png"}></img>
</a>
</h1>
<div
className="gnbWrap"
onMouseEnter={() => setHeight("250px")}
onMouseLeave={() => setHeight("0px")}
>
<div className="depthbg" style={{ height }}></div>
<ul className="gnb">
{menuData.map((menuItem) => (
<li key={menuItem.name}>
<a href={menuItem.link}>{menuItem.name}</a>
{/* Render submenu if it exists */}
{menuItem.subMenu && (
<ul className="depth" style={{ height }}>
{menuItem.subMenu.map((subItem) => (
<li key={subItem.name}>
<a href={subItem.link}>{subItem.name}</a>
</li>
))}
</ul>
)}
</li>
))}
</ul>
</div>
<div className="galBtn">
<a href="/frame">갤러리안내</a>
</div>
<ul className="h-icon">
<li>
<a href="#">
<i className="fa-brands fa-instagram"></i>
</a>
</li>
<li>
<a href="#">
<i className="fa-brands fa-youtube"></i>
</a>
</li>
<li>
<a href="#">
<i className="fa-brands fa-pinterest"></i>
</a>
</li>
</ul>
<div
className="hbgBtn"
onClick={() => setIsOpenHbgMenu(!isOpenHbgMenu)}
>
<span className="top"></span>
<span className="mid"></span>
<span className="bot"></span>
</div>
</div>
</div>
<div className={`hbgMenu ${isOpenHbgMenu ? "on" : ""}`}>
<div className="operci" onClick={() => setIsOpenHbgMenu(false)}></div>
<div className="nav">
<div className="xBtn" onClick={() => setIsOpenHbgMenu(false)}>
<span>x</span>
</div>
<ul className="hbgGnb">
{menuData.map((menuItem) => {
const isOpen = menuItem.name === openItem;
return (
<li
key={menuItem.name}
className="depthWrap2"
onClick={() => setOpenItem(isOpen ? null : menuItem.name)}
>
<a href={"#"}>{menuItem.name}</a>
{/* Render submenu if it exists */}
{menuItem.subMenu && (
<ul className={`depth2 ${isOpen ? "on" : ""}`}>
{menuItem.subMenu.map((subItem) => (
<li key={subItem.name}>
<a href={subItem.link}>{subItem.name}</a>
</li>
))}
</ul>
)}
</li>
);
})}
</ul>
<div className="hbgFoot">
<ul className="hbgIcon">
<li>
<a href="#">
<i className="fab fa-facebook-f color-white"></i>
</a>
</li>
<li>
<a href="#">
<i className="fab fa-instagram color-white"></i>
</a>
</li>
<li>
<a href="#">
<i className="fab fa-twitter color-white"></i>
</a>
</li>
</ul>
<p className="hbgCopy">© 2022 HS Kim AI 갤러리 Portfolio</p>
</div>
</div>
</div>
</>
);
};
export default Header;
처음에 쌩 자바스크립트를 변환하려다 보니 useRef를 남발하여 dom을 선택하고 useEffect 안에 js 함수들을 집어 넣는 방식으로 마이그레이션 하였으나 해당 방법은 리액트에서는 컴포넌트 구조다 보니까 치명적인 오류를 야기할 수 있어 상태 관리에 신경써줬습니다.
useState를 사용해도 되지만 확장성을 위해 리코일을 선택하였고
import { atom } from "recoil";
export const depthHeight = atom({
key: "depthHeight",
default: "0px"
});
export const hbgMenuOpen = atom({
key: "hbgMenuOpen",
default: false
});
export const openMenuItem = atom<string | null>({
key: "openMenuItem",
default: null
});
이렇게 상태 관리를 해줍니다.
홈에 Header 컴포넌트를 추가하고 확인해봅시다.
헤더입니다.
마우스엔터되면 드롭다운합니다. 해당 상태는 height 값으로 관리 됩니다.
반응형으로 줄어들었을때 햄버거 메뉴입니다.
햄버거 메뉴 클릭 및 x 버튼, 배경 클릭 을 관리하는 상태 값은 isOpenHbgMenu 입니다.
두번째 depth2 입니다. 해당값은 openItem 으로 상태 관리됩니다.
바닐라 자바스크립트와 리액트는 꽤나 다릅니다.
바닐라 자바스크립트가 돔조작을 하며 직관적이고 저에게 익숙하지만
리액트의 방식도 간결하고 사용하다보면 재밌습니다.
또한 dom조작을 직접 안하니까 성능이 좋죠.
다음 시간에 계속하겠습니다.
감사합니다.
'포트폴리오' 카테고리의 다른 글
[count]자바스크립트 (js) 코드 리액트 (react) 로 마이그레이션하기 (3) (0) | 2023.08.26 |
---|---|
[swiper, aos]자바스크립트 (js) 코드 리액트 (react) 로 마이그레이션하기 (2) (0) | 2023.08.26 |
React와 TensorFlow.js로 BTC 비트코인 예측 웹앱 만들기: 머신러닝 기반 프로젝트 튜토리얼 (0) | 2023.08.17 |
React와 TensorFlow.js로 당뇨병 예측 웹앱 만들기: 머신러닝 기반 프로젝트 튜토리얼 (0) | 2023.08.09 |
포트폴리오 리뷰: AI 갤러리 프로젝트 - Node.js, MongoDB, EJS 템플릿 엔진을 활용한 웹 개발 (0) | 2023.07.20 |
댓글