React 4

j0yy00n0·2025년 5월 15일
post-thumbnail

2025.04.15 ~ 04.16

React

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

  • npm create vite@latest [디렉토리 명]
  • Node.js 업데이트가 필요한 경우 y 실행
  • 지정한 디렉토리가 이미 존재하고 비어있지 않은 경우
  • 방향키로 원하는 framework를 선택한 후 enter를 누른다
  • 방향키로 원하는 언어를 선택한다.
  • 실행이 완료 되면 아래와 같은 화면이 실행된다.
  • 안내된 명령어를 그대로 따라 입력한다.
  • node_modules : 설치된 외부 라이브러리들의 실제 코드가 저장되는 폴더, 라이브러리 파일, 클래스, 트랜스 파일링 툴
  • package.json : build.gradle처럼 프로젝트의 메타 정보와 의존성 정보 관리 파일
  • npm run 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

Router

클라이언트(사용자)의 요청 URL 경로에 따라 어떤 컴포넌트(페이지)를 보여줄지 매핑(연결)하는 역할을 하는 시스템

  • 리액트에서는 요청에 따라 요청에 매핑 된 컴포넌트를 Rendering 하게 된다
  • 전통적인 서버 기반 웹은 요청마다 서버에서 HTML을 새로 반환
  • React는 SPA(Single Page Application)이기 때문에 페이지 전체를 새로고침하지 않고 컴포넌트만 바꿔 끼운다
  • 리액트 라우터는 요청 경로에 따라 컴포넌트를 동적으로 렌더링

React Router Dom

리액트에서 라우팅을 구현하기 위한 대표적인 라이브러리

  • 명령어 입력 : npm install react-router-dom
  • react-router-dom 의 주요 모듈
  1. BrowserRouter : 라우팅 기능을 활성화하는 상위 컴포넌트. 라우팅이 필요한 컴포넌트들을 으로 감싸서 사용
  2. Routes : Route 들을 묶어주는 단위 컴포넌트, 여러 개의 < Route>를 그룹핑하여 라우터 매핑을 정의하는 영역
  3. Route : URL 요청 주소와 컴포넌트를 매핑해주는 단위 컴포넌트
  • @6 : 6버전으로 설치
  • /root 경로 요청이 들어오면 메인 페이지가 화면에 렌더링된다.
  • App()이라는 컴포넌트가 HTML 파일 내에서 단 한 번만 렌더링되며, 이 안에서 라우팅 기능을 구성한다.
  • react-router-dom의 BrowserRouter, Routes, Route를 사용하여 경로별로 다른 컴포넌트를 렌더링
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 사용

  • NavLink 컴포넌트는 Link와 사용법은 거의 유사
  • 현재 Nav 의 상태가 active 인 지에 대한 값을 이용하여 스타일을 조작하거나 클래스 이름을 변경할 수 있다
  • 스타일에 대한 속성을 넣을 수 있다.
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;

Layout

Outlet도 react-router-dom에서 제공하는 기능

  • import {Outlet} from 'react-router-dom';
  • 자식 라우트 컴포넌트가 렌더링될 자리를 표시하는 JSX 컴포넌트
  • 요청에 따라서 중첩라우팅을 구현할 때 필수 기술
  • 중첩 라우트를 사용할 때, 부모 컴포넌트(Layout) 안에 Outlet을 배치하면, URL에 따라 해당 자식 컴포넌트가 해당 위치에 표시
  • 자식 라우트 컴포넌트는 App.jsx에서 Route 구조로 정의, Layout은 이 구조의 부모 라우트 요소로 설정
import Header from "../components/Header";
import Navbar from "../components/Navbar";
import {Outlet} from 'react-router-dom';

function Layout(){
    return(
        <>
            <Header/>
            <Navbar/>
            <Outlet/> /* 자식 라우트가 여기에 렌더링 된다.*/
        </>
    )
}

App

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
  • index는 부모 경로 / 요청 시 렌더링
  • path="main"은 /main 요청 시 렌더링

Error

에러 전용 컴포넌트를 만들어서 예외 상황에 대응

  • 예를 들어 fetch()로 데이터를 요청했을 때 응답이 실패하거나, 존재하지 않는 URL로 접근하는 경우 404 페이지로 유도할 수 있다.
  • 클라이언트 라우팅 방식에서는 백엔드에서 받은 상태 코드나 경로에 따라 어떤 컴포넌트를 보여줄지 직접 정의할 수 있다.
function Error404(){

    return(
        <>
            <div>
                <h1>404 NOT FOUND EXCEPTION </h1>
                <h3>준비가 안 된 페이지 입니다.. </h3>
            </div>
        </>
    )
}

export default Error404;

App.jsx

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

Params


화면 렌더링 시 전체 조회

  • .json 파일을 JS에서 불러와 함수로 외부에 제공
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;

특정 화면 상세 페이지 이동

  • 클릭하면 조회할 페이지를 Link로 uri를 설정한다.
    사용자가 클릭하면 해당 메뉴의 menuCode를 포함한 경로로 이동
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;
  • .json 파일을 JS에서 불러와 함수로 외부에 제공
    원하는 설정으로 데이터 구조를 만들어서 가져온다.
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];
}

검색 기능

  • 입력값 받기
    navigate(/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;
  • .json 파일을 JS에서 불러와 함수로 외부에 제공
    원하는 설정으로 데이터 구조를 만들어서 가져온다.
import menus from "../data/menu-detail.json";

export function searchMenu(menuName){
    /*
        match() 메서드
        포함 여부에 따라서 인수 값이 포함되어 있으면
        객체를 반환한다.
    */

    // 검색어인 searchMenuName 에 포함되는 값을 가지는 menu 객체를 반환
    return menus.filter(menu => menu.menuName.match(searchMenuName))
}

Query String VS Path Parameter

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 프레임워크/라이브러리

  • React.js
  • Vue.js
  • Angular
  • 이 라이브러리들의 특징은 SPA(Single Page Application)이다.
  • 전통적인 방식(예: Thymeleaf, JSP 등)은 요청마다 새로운 HTML 파일을 서버에서 렌더링해 전달
  • SPA는 index.html 하나를 기반으로 JavaScript와 컴포넌트를 동적으로 렌더링하여 화면을 구성

CSR(Client Side Rendering) :브라우저에서 모든 렌더링을 수행하여 초기 로딩은 느리지만 이후 빠른 전환이 가능
SSR(Server Side Rendering): 서버에서 HTML을 렌더링하여 초기 속도가 빠르다

터미널 명령어

  • cd : change directory
  • cd .. : 상위 디렉토리로 이동
  • md 디렉토리명 : make directory (mkdir)
  • rm 디렉토리명 : remove directory (rmdir)

해당하는 어플리케이션 종료

  • ctrl + c

코드 한줄 복사

  • alt + shift + 방향키

React 프로그램 작성 시 mode_modeules를 무겁기 때문에 만약 당장 실행시킬 것이 아니라면 삭제해도 된다.
다시 실행 시 npm install로 설치하면 된다.

profile
잔디 속 새싹 하나

0개의 댓글