Single Page Application / React Router

nara_lee·2025년 3월 14일
1

Single Page Application (SPA)란?

  • html 을 서버 측에서 한번만 받아와서 웹 애플리케이션을 실행시킨 후, 이후에는 필요한 데이터만 받아와서 화면에 업데이트하는 것
    => 리액트 같은 라이브러리를 사용하여 뷰 렌더링을 사용자의 브라우저가 담당

  • 링크를 눌러 다른 페이지로 이동할 때 서버에 다른 페이지의 html을 새로 요청하는 것이 아닌, 브라우저의 History API를 사용하여 브라우저의 주소창의 값만 변경하고 기존에 페이지에 띄웠던 웹 앱을 그대로 유지하면서 라우팅 설정에 따라 또 다른 페이지를 보여주게 됨.

만들어보자!

//index.js
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import { BrowserRouter } from "react-router-dom";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>
);

💡 BrowserRouter 가 SPA를 가능케 함.

  1. BrowserRouter 로 감싸야만 Routes/Route 쓸 수 있음! <BrowserRouter> wraps <App />, which means that all components inside <App /> will have access to React Router functionalities (like navigating between pages). This allows the app to use route-based navigation (<Route>, <Link>, useNavigate(), etc.) without needing full-page reloads.

  1. What Exactly Does <BrowserRouter> Do?
    ✅ Manages the URL history (without full-page reloads).
    ✅ Listens for URL changes and renders the correct page/component.
    ✅ Provides routing context to all components inside <App />.

  1. How It Works:
    Normally, in a traditional website, when you click a link, the browser makes a request to the server for a new page.
    With React Router (<BrowserRouter>), URL changes are handled inside JavaScript without making a new server request.
    This makes React apps faster since only the necessary components are updated instead of reloading the entire page.
//App.js
import { Route, Routes } from "react-router-dom";
import About from "./pages/About";
import Home from "./pages/Home";

function App() {
  return (
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/about" element={<About />} />
    </Routes>
  );
}

export default App;
//Home.js
import React from "react";
import { Link } from "react-router-dom";

const Home = () => {
  return (
    <div>
      <h1>Home</h1>
      <p>First Page</p>
      <Link to="/about">About</Link>
    </div>
  );
};

export default Home;

유동적인 값을 페이지 주소로 쓰기

방법 1. URL parameter

//App.js
import { Route, Routes } from "react-router-dom";
(...)
 <Routes>
 	(...)
	<Route path="/profile/:username" element={<Profile />} />
 </Routes>
//Home.js
import { Link } from "react-router-dom";
(...)
    <div>
      <ul>
        <li>
          <Link to="/profile/velopert">velopert</Link>
        </li>
        <li>
          <Link to="/profile/gildong">gildong</Link>
        </li>
        <li>
          <Link to="/profile/unknown">존재하지 않는 프로필</Link>
        </li>
      </ul  >
    </div>
  );
};
import React from "react";
import { useParams } from "react-router-dom";

const data = {
  velopert: {
    name: "김민준",
    description: "리액트를 좋아하는 개발자",
  },
  gildong: {
    name: "홍길동",
    description: "고전 소설 홍길동전의 주인공",
  },
};

const Profile = () => {
  const params = useParams();
  const profile = data[params.username];
  return (
    <div>
      <h1> 사용자 프로필 </h1>
      {profile ? (
      <div>
        <h3>
          {params.username} ({profile.name})
        </h3>
        <p>{profile.description}</p>
      </div>
      ) : (<div>존재하지 않는 사용자입니다.</div>)}
    </div>
  );
};

export default Profile;

useParams, Link 를 react-router-dom 에서 import 하자!

💡 여기서 갑자기 든 의문!data 같은 독립적인 객체를 Profile Component 안에 선언하는 것과 밖에 선언하는 것의 차이점은 뭘까? 당연히 component에서 일어나는 event handling 함수 같은 경우에는 component 안에 써야한다. 그렇다면 data 객체는 왜 밖에 썼을까? 만약 component 안에 썼다면 Profile component가 렌더링 될 때마다 새로운 data 객체가 생성 됐을 것이기 때문에 비효율적이다.

방법 2. Query string

💡 URL parameter 와 달리 Route 컴포넌트 사용시 별도로 설정해야 하는 것이 없다.

http://localhost:3000/about?detail=true&mode=1 라는 쿼리 스트링
useSearchParams이 ?을 지우고 &로 분류한 뒤 뒤 key와 value값을 =로 파싱함.

Nested Routes

//Before App.js
      <Route path="/articles" element={<Articles />} />
      <Route path="/article/:id" element={<Article />} />
//After App.js
      <Route path="/articles" element={<Articles />}>
        <Route path=":id" element={<Article />} />
      </Route>
// Articles.js
<div>
  <Outlet />
  <ul>
    <li>
      <Link to="/articles/1">Article 1</Link>
    </li>
    (...)
  </ul>
</div>

Outlet/> 을 쓰면 <Route path=":id" element={<Article />} /> 를 보여줌. (Route의 children 으로 들어가는 JSX element 를 보여주는 역할)

Article page 를 열었을 때 Article List가 보이는 것을 볼 수 있다!

공통 레이아웃 컴포넌트

//App.js
<Route element={<Layout />}>
  <Route index element={<Home />} />
  <Route path="/about" element={<About />} />
  <Route path="/profile/:username" element={<Profile />} />
</Route>
import React from "react";
import { Outlet } from "react-router-dom";

const Layout = () => {
  return (
    <div>
      <header style={{ background: "lightgray", padding: 16, fonstsize: 24 }}>
        header
      </header>
      <main>
        <Outlet />
      </main>
    </div>
  );
};

export default Layout;

이런 식으로 Outlet를 이용하면 "home", "about", "profile" 페이지에서 Layout component 를 보여줄 수 있다.

리액트 라우터 부가 기능

useNavigate

const Layout = () => {
  const navigate = useNavigate();

  const goBack = () => {
    navigate(-1);
  };
  const goArticles = () => {
    navigate("/articles", { replace: true });
  };
  return (
    <div>
      <header style={{ background: "lightgray", padding: 16, fonstsize: 24 }}>
        <button onClick={goBack}>뒤로가기</button>
        <button onClick={goArticles}>게시글로 이동</button>
        
  (...)

-1 은 한번 뒤로가기 2은 두번 앞으로가기
replace: true 옵션은 페이지 이동 시 현재 페이지를 페이지 기록에 남기지 않는다.

react-router-domLink component 대신 쓸 수 있다.
링크에서 사용하는 경로가 현재 라우트의 경로와 일치하는 경우 특정 스타일 또는 CSS 클래스를 적용하는 컴포넌트

Wrapping Component로 감싸서 반복되는 코드를 리팩터링 해보자

//Before
import React from "react";
import { NavLink, Outlet } from "react-router-dom";

const Articles = () => {
  const activeStyle = {
    color: "green",
    fontsize: "21",
  };
  return (
    <div>
      <Outlet />
      <ul>
        <li>
          <NavLink
            to="/articles/1"
            style={({ isActive }) => (isActive ? activeStyle : undefined)}
          >
            Article 1
          </NavLink>
        </li>
        <li>
          <NavLink
            to="/articles/2"
            style={({ isActive }) => (isActive ? activeStyle : undefined)}
          >
            Article 2
          </NavLink>
        </li>
        <li>
          <NavLink
            to="/articles/3"
            style={({ isActive }) => (isActive ? activeStyle : undefined)}
          >
            Article 3
          </NavLink>
        </li>
      </ul>
    </div>
  );
};

export default Articles;
//After
import React from "react";
import { NavLink, Outlet } from "react-router-dom";

const Articles = () => {
  return (
    <div>
      <Outlet />
      <ul>
        <ArticleItem id={1} />
        <ArticleItem id={2} />
        <ArticleItem id={3} />
      </ul>
    </div>
  );
};
const ArticleItem = ({ id }) => {
  const activeStyle = {
    color: "green",
    fontsize: "21",
  };
  return (
    <li>
      {" "}
      <NavLink
        to="/articles/1"
        style={({ isActive }) => (isActive ? activeStyle : undefined)}
      >
        Article 1
      </NavLink>
    </li>
  );
};
export default Articles;

NavLink 의 style과 className은 {isActive: boolean}을 파라미터로 전달바는 함수 타입의 값을 전달한다.

<NavLink
  to="/articles/1"
  style={({ isActive }) => (isActive ? activeStyle : undefined)}
/>

NotFound 페이지 만들기

<Route path="*" element={<div>404 Not Found</div>} />

이 라우트 element 상단에 위치하는 사우트들의 규칙을 모두 확인하고, 일치하는 라우트가 없다면 이 라우트가 화면에 나타난다.

따라서 path="*"는 모든 Route Component들 중 최하단에 서 불러야한다.

페이지를 리다이렉트하고 싶을 때

사용자의 로그인이 필요한 페이지인데 로그인을 안했다면 로그인 페이지로 리다이렉트 해야한다!!

//Login.js
import React from 'react';

export default const Login = () => {
    return (
        <div>
            로그인 페이지
        </div>
    );
};
//MyPage.js
import React from 'react';
import { Navigate } from 'react-router-dom';

export default MyPage = () => {
    const isLoggedIn = false;
    if (!isLoggedIn) return <Navigate to="/login" />;
    return (
        <div>
            마이 페이지
        </div>
    );
};

본 후기는 [한글과컴퓨터x한국생산성본부x스나이퍼팩토리] 한컴 AI 아카데미 (B-log) 리뷰로 작성 되었습니다.

#한컴AI아카데미 #AI개발자 #AI개발자교육 #한글과컴퓨터 #한국생산성본부 #스나이퍼팩토리 #부트캠프 #AI전문가양성 #개발자교육 #개발자취업

0개의 댓글