2025.04.15 ~ 04.16
React 어플리케이션을 만들 때 CRA(Create React APP)를 많이 사용하였지만 현재 공식적으로 유지보수가 중단되었다.
대신 Vite로 React APP를 구성하는 방식이 대세가 되고 있다.
CRA - Webpack을 사용하여 빌드하며, 속도가 느리고 React 중심의 구조
Vite - Rollup을 기반으로 빠르게 번들링하며, React는 물론 다양한 프레임워크(Vue, Svelte 등)를 지원한다. 자체 dev 서버와 빌드 툴을 가지고 있어 개발 속도와 빌드 속도가 매우 빠르다.











동작 방식
App.jsx -> 컴포넌트 정의 후 eport default 를 사용해서 import를 할 수 있게 한다.

main.jsx -> App을 import해서 ReactDOM.createRoot(...).render()로 렌더링

index.html -> < div id="root">< /div>를 통해 main.jsx에서 렌더링 위치를 제공

삭제해도 되는 부분 : assets, index.css, app.css
초기세팅 main.jsx

초기 세팅 App.jsx

클라이언트(사용자)의 요청 URL 경로에 따라 어떤 컴포넌트(페이지)를 보여줄지 매핑(연결)하는 역할을 하는 시스템
리액트에서 라우팅을 구현하기 위한 대표적인 라이브러리

import {BrowserRouter, Routes, Route} from 'react-router-dom';
import Main from './pases/Main';
import Menu from './pases/Menu';
import Order from './pases/Order';
function App() {
/* path='/'로 설정하면 루트 경로 요청 시 요청 컴포넌트가 렌더링 */
return (
<>
<BrowserRouter>
<Routes>
{/* <Route index element={<Main/>}/> */}
<Route path='/' element={<Main/>}/>
<Route path='/menu' element={<Menu/>}/>
<Route path='/order' element={<Order/>}/>
</Routes>
</BrowserRouter>
</>
)
}
export default App
/*path='/' 와 index 부분이 같은 의미다 */
pages/ : 라우팅으로 보여지는 화면 단위 컴포넌트, content
components/ : 재사용이 가능한 컴포넌트들을 모아놓은 곳, Header, Sidebar, Footer, Navbar 등
layouts/ : 화면 틀 구성, 페이지 전체의 공통 레이아웃 구조
Link 사용
import {Link} from "react-router-dom";
function Navbar() {
const activeStyle = {
backgroundColor : 'red',
color : 'white'
}
return(
<>
<div>
<ul>
<li><Link to="/main">메인</Link></li>
<li><Link to="/menu">메뉴</Link></li>
<li><Link to="/order">주문</Link></li>
</ul>
</div>
</>
)
}
export default Navbar;
NavLink 사용
import {NavLink} from "react-router-dom";
function Navbar() {
const activeStyle = {
backgroundColor : 'red',
color : 'white'
}
return(
<>
<div>
<ul>
<li><NavLink to="/main" style={({isActive}) => isActive ? activeStyle : undefined}>메인</NavLink></li>
<li><NavLink to="/menu" style={({isActive}) => isActive ? activeStyle : undefined}>메뉴</NavLink></li>
<li><NavLink to="/order" style={({isActive}) => isActive ? activeStyle : undefined}>주문</NavLink></li>
</ul>
</div>
</>
)
}
export default Navbar;
Outlet도 react-router-dom에서 제공하는 기능
import Header from "../components/Header";
import Navbar from "../components/Navbar";
import {Outlet} from 'react-router-dom';
function Layout(){
return(
<>
<Header/>
<Navbar/>
<Outlet/> /* 자식 라우트가 여기에 렌더링 된다.*/
</>
)
}
import {BrowserRouter, Routes, Route} from "react-router-dom";
import Layout from "./layouts/Layout";
import Main from "./pages/Main";
import Menu from "./pages/Menu";
import Order from "./pages/Order";
function App() {
return (
<BrowserRouter>
<Routes>
/* Layout 구조에 자식 컴포넌트들을 넣어서 배치한다.
Layout이 공통 틀이고, 그 안에서 자식 컴포넌트들이 Outlet으로 들어감
*/
<Route path="/" element={<Layout/>}>
<Route index element={<Main/>}/>
<Route path="main" element={<Main/>}/>
<Route path="menu" element={<Menu/>}/>
<Route path="order" element={<Order/>}/>
</Route>
</Routes>
</BrowserRouter>
)
}
export default App
에러 전용 컴포넌트를 만들어서 예외 상황에 대응
function Error404(){
return(
<>
<div>
<h1>404 NOT FOUND EXCEPTION </h1>
<h3>준비가 안 된 페이지 입니다.. </h3>
</div>
</>
)
}
export default Error404;
import {BrowserRouter, Routes, Route} from "react-router-dom";
import Layout from "./layouts/Layout";
import Main from "./pages/Main";
import Menu from "./pages/Menu";
import Order from "./pages/Order";
import Error404 from "./errors/Error404";
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Layout/>}>
<Route index element={<Main/>}/>
<Route path="main" element={<Main/>}/>
<Route path="menu" element={<Menu/>}/>
<Route path="order" element={<Order/>}/>
</Route>
/* 설정하지 않은 경로로 사용자가 입력하게 되는 모든 입력값(*)을 보고 에러 컨포넌트를 실행*/
<Route path="*" element={<Error404/>}/>
</Routes>
</BrowserRouter>
)
}
export default App
import menus from "../data/menu-detail.json";
// 메뉴 데이터 전체 조회
// js도 다른 파일에서 사용하려면 export를 통해 외부로 내보내야한다.
export function getMenuList(){
console.log('menus', menus);
return menus;
}
import { useState, useEffect } from "react";
import { getMenuList } from "../apis/MenuAPI";
import MenuItem from "../items/MenuItem";
function Menu() {
// 관리 할 메뉴 배열
const [menuList, setMenuList] = useState([]);
useEffect(() => {
/* Menu 컴포넌트가 화면에 렌더링된 직후 실행됨
메뉴 데이터를 가져와서 상태값(menuList)에 설정
*/
setMenuList(getMenuList());
}, [])
return(
<>
<div className={boxStyle.MenuBox}>
{menuList.map(menu => <MenuItem key={menu.menuCode} menu={menu}/>)}
</div>
</>
)
}
export default Menu;
import itemStyle from "./MenuItem.module.css";
// 구조 분해 할당을 통해 menu 객체를 props로 받음
function MenuItem({menu}){
return(
<div className={itemStyle.menuItem}>
<h3>메뉴 이름 : {menu.menuName}</h3>
<h3>메뉴 가격 : {menu.menuPrice}</h3>
<h3>메뉴 종류 : {menu.categoryName}</h3>
</div>
)
}
export default MenuItem;

import { Link } from "react-router-dom";
import itemStyle from "./MenuItem.module.css";
// 구조분해 할당으로 넘겨받은 menu 정보
function MenuItem({menu}){
return(
<Link to={`/menu/${menu.menuCode}`}>
<div className={itemStyle.MenuItem}>
<h3>메뉴 이름 : {menu.menuName}</h3>
<h3>메뉴 가격 : {menu.menuPrice}</h3>
<h3>메뉴 종류 : {menu.categoryName}</h3>
</div>
</Link>
)
}
export default MenuItem;
import { useState, useEffect } from "react";
// useParams : URL의 path variable을 추출해주는 훅
import { useParams } from "react-router-dom";
// 메뉴 상세 컴포넌트
function MenuDetail(){
const {menuCode} = useParams();
const [menu, setMenu] = useState({
menuName : '',
menuPrice : 0,
categoryName : '',
detail : {}
});
useEffect(() =>{
setMenu(getMenuByMenuCode(menuCode));
}, []);
return(
<>
<h1>{menu.menuName} 상세페이지!</h1>
<h3>메뉴 가격 : {menu.menuPrice}</h3>
<h3>메뉴 종류 : {menu.categoryName}</h3>
<h3>메뉴 설명 : {menu.detail.description}</h3>
<img src={menu.detail.image} style={{maxWidth: 500}}/>
</>
);
}
export default MenuDetail;
import { BrowserRouter, Route, Routes } from "react-router-dom";
import Layout from "./layouts/Layout"
import Main from "./pages/Main"
import About from "./pages/About";
import Menu from "./pages/Menu";
import MenuDetail from "./pages/MenuDetail";
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Layout/>}>
<Route index element={<Main/>}/>
<Route path="main" element={<Main/>}/>
<Route path="about" element={<About/>}/>
<Route path="menu">
<Route index element={<Menu/>}/>
{/* path variable 받는 방법 */}
<Route path=":menuCode" element={<MenuDetail/>}/>
</Route>
</Route>
</Routes>
</BrowserRouter>
)
}
export default App;
import menus from "../data/menu-detail.json";
// 메뉴 데이터 상세 조회
export function getMenuByMenuCode(menuCode){
/*
pathVariable menuCode 를 전달 받고 있다.
단 여기서 주의점, URL 데이터는 문자열
*/
/*
filter 메서드
menus 배열에서 콜백함수가 true 인 요소만 배열로 반환해주는 기능
*/
// filter는 조건에 맞는 모든 항목을 배열로 반환하므로 [0]으로 첫 번째 항목만 꺼냄
return menus.filter(menu => menu.menuCode === parseInt(menuCode))[0];
}
/menu/search?menuName=${searchValue})import { useState, useEffect } from "react";
import { getMenuList } from "../apis/MenuAPI";
import MenuItem from "../items/MenuItem";
import boxStyle from "./Menu.module.css";
import { useNavigate } from "react-router-dom";
// useNavigate : 여러 군데로 이동 시켜주는 훅훅
function Menu() {
// 관리 할 메뉴 배열
const [menuList, setMenuList] = useState([]);
// 관리할 input 값
const [searchValue , setSearchValue] = useState("");
// 네비게이션 기능 사용할 변수 선언
const navigate = useNavigate();
useEffect(() => {
/* Menu 컴포넌트가 마운트 되기 전에 데이터를 가져와 state에 담기 */
setMenuList(getMenuList());
}, [])
const onChangeHandler = (e) => {
setSearchValue(e.target.value);
}
// 쿼리스트링 파라미터로 검색값 전달
const onClickHandler = () =>{
navigate(`/menu/search?menuName=${searchValue}`)
}
return(
<>
<h1>판매 가능한 메뉴!</h1>
<div>
<input type="search" name="menuName" onChange={onChangeHandler}/>
<button onClick={onClickHandler}>검색</button>
</div>
<div className={boxStyle.MenuBox}>
{menuList.map(menu => <MenuItem key={menu.menuCode} menu={menu}/>)}
</div>
</>
)
}
export default Menu;
function MenuSearchResult(){
return(
<>
<h1>검색 결과!</h1>
</>
)
}
export default MenuSearchResult;
import { BrowserRouter, Route, Routes } from "react-router-dom";
import Layout from "./layouts/Layout"
import Main from "./pages/Main"
import About from "./pages/About";
import Menu from "./pages/Menu";
import MenuDetail from "./pages/MenuDetail";
import MenuSearchResult from "./pages/MenuSearchResult";
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Layout/>}>
<Route index element={<Main/>}/>
<Route path="main" element={<Main/>}/>
<Route path="about" element={<About/>}/>
<Route path="menu">
<Route index element={<Menu/>}/>
{/* pathvarable 받는 방법 */}
<Route path=":menuCode" element={<MenuDetail/>}/>
<Route path="search" element={<MenuSearchResult/>}/>
</Route>
</Route>
</Routes>
</BrowserRouter>
)
}
export default App;

import { useState, useEffect } from "react";
// 쿼리스트링 값 꺼낼 때 사용하는 hooks
// path variable -> useParams
import { useSearchParams } from "react-router-dom";
function MenuSearchResult(){
/*
쿼리스트링 파라미터는 여러 값들을 전달할 수 있기 때문에
배열 형태로 key-value 로 저장이 되어 있다.
*/
const [searchParam] = useSearchParams();
console.log(searchParam);
// 쿼리스트링 값 추출 방법 get("key")
const menuName = searchParam.get('menuName');
// 검색 된 메뉴 관리 할 상태값 배열
const [menuList, setMenuList] = useState([]);
useEffect(() => {
setMenuList(searchMenu(menuName));
}, []);
return(
<>
<h1>검색 결과!</h1>
<div>
{menuList.map(menu => <MenuItem key={menu.menuCode} menu={menu}/>)}
</div>
</>
)
}
export default MenuSearchResult;
import menus from "../data/menu-detail.json";
export function searchMenu(menuName){
/*
match() 메서드
포함 여부에 따라서 인수 값이 포함되어 있으면
객체를 반환한다.
*/
// 검색어인 searchMenuName 에 포함되는 값을 가지는 menu 객체를 반환
return menus.filter(menu => menu.menuName.match(searchMenuName))
}

Query String
// URL: /menu/101
<Route path="/menu/:menuCode" element={<MenuDetail />} />
// MenuDetail.jsx
const { menuCode } = useParams();
Path Parameter
// URL: /menu?category=한식&price=5000
const [searchParams] = useSearchParams();
const category = searchParams.get("category");
const price = searchParams.get("price");
프론트엔드 서버에서 대표적인 SPA 프레임워크/라이브러리
CSR(Client Side Rendering) :브라우저에서 모든 렌더링을 수행하여 초기 로딩은 느리지만 이후 빠른 전환이 가능
SSR(Server Side Rendering): 서버에서 HTML을 렌더링하여 초기 속도가 빠르다
터미널 명령어
해당하는 어플리케이션 종료
코드 한줄 복사
React 프로그램 작성 시 mode_modeules를 무겁기 때문에 만약 당장 실행시킬 것이 아니라면 삭제해도 된다.
다시 실행 시 npm install로 설치하면 된다.