첫 SPA

taehyung·2023년 10월 17일

React.js

목록 보기
15/24
post-thumbnail

라우팅이란?

사용자에 요청에 따라 서버에서 알맞은 페이지를 보여주는것, 데이터가 이동할 경로를 정해주는 행위 자체와 과정들을 일컫는 말.

쉽게말해 사용자의 요청 즉, 경로(URL)에 따라 컴포넌트를 갈아끼워주는 것

소규모 프로젝트는 하나의 페이지로도 충분하지만, 프로젝트의 사이즈가 커질수록 페이지를 분리해야합니다.

게시판을 예를들어볼까요?

  • Write : 게시글 작성 페이지
  • List : 게시글 목록 페이지
  • View : 게시글 페이지

페이지 갯수가 늘어나면 컴포넌트들을 분리해가며 관리해야 하는데요, 이 때 필요한 것이 라우팅 시스템입니다.

리액트에서 라우트 시스템을 구축하기위해서는 크게 두가지 선택지가 있습니다.

  • 리액트 라우터
    리액트라우팅 관련 라이브러리중 가장 오래됐고, 가장 많이 사용되고 있습니다. 컴포넌트 기반 라우팅 시스템을 설정할 수 있습니다.

  • Next.js
    리액트 프로젝트의 프레임워크, 리액트 프로젝트를 설정할 수 있습니다. 라우팅 시스템, 최적화, 다국어 시스템, SSR 등 다양한 기능을 제공합니다. 리액트 라우터의 대안으로 많이 사용합니다.

SPA란?

SPA란 하나의 페이지로 이루어진 애플리케이션을 의미합니다.

페이지를 라우팅하여 여러 페이지로 만든다면서요?

이를 이해하기 위해서는 예전 방식인 멀티페이지 어플리케이션을 알아야합니다.

SPA와 MPA의 차이

MPA ( SSR )
1. 페이지 전환시마다 필요한 페이지,JS,CSS 요청 -> 응답
장점 : 유저 인터렉션이 적은 정적페이지에 유리 , 초기 렌더링속도 빠름, SEO 가능
단점 : 사용되는 자원량, 트래픽 증가
SPA( CSR )
1. 단일페이지 사용, 유저의 인터렉션이 발생하면 필요한 데이터만 요청 -> 응답
장점 : 사용되는 자원량, 트래픽 감소
단점 : 초기 로딩속도 느림

페이지 이동에 따라 서버와 통신을 하느냐 안하느냐가 가장 중요합니다.
SPA 페이지의 변환을 클라이언트 측에서 해결합니다.
MPA 페이지의 변환을 서버 측에서 해결합니다.

페이지 라우팅을 하는 이유

SPA는 하나의 페이지를 가진 어플리케이션 이라는 뜻입니다. 하나의 페이지에서 모든걸 다 하는 경험을 해보신적이 있나요? 아마 없을것입니다.

  • 페이지 라우팅은 SPA를 마치 MPA 인것처럼 구현하기위해 사용합니다. ( 사용자 경험 측면, 하나의 페이지의 데이터 방대해짐을 막는데 유리 )
  • 작성중 ...

이번 포스팅은 리액트 라우터를 사용해 작성하겠습니다.

리액트라우터 라이브러리를 다운받습니다.

turminal


	npm install react-router-dom

index.js


import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { BrowserRouter } from "react-router-dom";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>
);
reportWebVitals();

BrowserRouter
<BrowserRouter>컴포넌트는 HTML5 History API를 기반으로 브라우저의 URL과 상호작용하여 페이지 전환시 HTML요청 없이 History Api로 페이지 전환 효과를 보여줍니다. 일반적으로 애플리케이션의 최상위 컴포넌트로 사용됩니다.


<Link>


//Home.js
import { Link } from "react-router-dom";

const Home = () => {
return (
  <div>
  	<p>메인페이지 입니다.</p>
  	<Link to="/about">소개페이지</Link>
  </div>
);

리액트에서는 <a> 태그를 사용할 수 없습니다.
왜냐구요? <a> 태그는 HTTP 요청을 다시하게되고, 페이지가 리다이렉션 되기때문에 SPA가 아닌 MPA방식으로 작동되기 때문입니다.

<a> 태그는 외부 페이지로 이동하거나, 새 페이지를 열 때만 사용합니다.

Link 컴포넌트는 페이지 간의 하이퍼링크를 만드는 데 사용됩니다. 예를 들어, <Link to="/about">소개페이지</Link> 는 /about 경로로 이동하는 링크를 생성합니다.


Routes, Route

  • Routes : 보여줄 컴포넌트 목록을 내부에 가지고 있고, URL에 따라 보여줄 컴포넌트를 결정해주는 컴포넌트
    • 내부에 여러개의 Route 컴포넌트 목록을 가질 수 있습니다.
    • 여러개의 Route 컴포넌트로 중첩구조도 가질 수 있습니다.
    • <Routes> 라우트 목록.. </Routes> 와 같이 사용합니다.
    • Tip. " 어디보자.. path가 about 잖아? 내가 가진 <Route> 중에 about 보여줘야징 ~ "
  • Route : path 경로와 보여줄 컴포넌트를 매핑해주는 컴포넌트
    • 단일 경로와 해당 경로에 대한 처리 컴포넌트를 정의하는 데 사용됩니다.
    • <Route path="경로" element={<보여줄 컴포넌트>}> 와 같이 사용합니다.
    • "Tip. <About/> 컴포넌트는 /about 경로랑 매핑해"
    • index 라는 props 를 가지고있고 path="/" 와 같은 기능을 합니다.

//App.js
import { Route, Routes } from "react-router-dom";
import About from "./pages/About";
import Home from "./pages/Home";

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

URL파라미터와 쿼리스트링

URL은 단순히 경로만 보여주고 화면을 변경시켜주는것에 그치지 않습니다.

변경될 화면(경로)에 데이터를 함께 넘겨줄 수 있는 능력이 있는데요,그 종류는 두 가지가 있습니다.

  • URL파라미터 (Path Variable) : /about/taehyung
    • 주로 ID,이름을 사용하여 특정 데이터 조회할 때 사용
  • 쿼리스트링 (Query String ): /about?name=taehyung&age=30
    • 주로 키워드검색, 페이지네이션, 정렬 방식 등 데이터 조회에 필요한 옵션을 전달할 때 사용

URL파라미터 (Path Variable)

URL파라미터는 <Route> , <Link> , useParams 의 삼박자에의해 만들어집니다.

App.js

import { Route, Routes } from "react-router-dom";
import Home from "./pages/Home";
import Profile from "./pages/Profile";

function App() {
  return (
    <Routes>
      <Route path="/" element={<Home />}></Route>
      <Route path="/profiles/:username" element={<Profile />}></Route>
    </Routes>
  );
}

export default App;

<Route>
Profile 컴포넌트의 path 속성에 집중해주세요.
Profile 컴포넌트는 /profiles 라는 경로가 주어지면 렌더링 됩니다.
Profile 컴포넌트는 :username 이라는 URL파라미터를 반드시 받습니다. ( 빈 값이 온다면 렌더링하지 않습니다. 별도처리 필요 )

Home.js

import React from "react";
import { Link } from "react-router-dom";

const Home = () => {
  return (
    <div>
      <h1></h1>
   		<Link to="/profiles/taechu">태형이</Link>
		<Link to="/profiles/void">존재하지않는프로필</Link>
    </div>
  );
};

export default Home;

<Link>
<Link> 컴포넌트의 to 속성에 집중해주세요.
App.js 파일에서 "/profiles/:username" 속성을 설정했었죠?
<Link> 의 to "/profiles/taechu" 처럼 경로가 매칭되는것이 보이시나요?

버튼을 클릭하여 링크로 이동할 때 매칭된 변수명과 값은 { username: taechu } 처럼 객체로 반환되며 링크된 페이지에서 useParams Hook으로 조회가 가능합니다.

Route = 변수명정의 , Link = 값 정의 까지 됐습니다. 그렇다면 실제 보여질 컴포넌트는 어떤 일을 할까요?

Profile.js

import { useParams } from "react-router-dom";

const data = {
  taechu: {
    name: "김태형",
    description: "안녕하세요",
  }
};

const Profile = () => {
  const params = useParams();
  const profile = data[params.username]; //객체 대괄호 표기법 data.taechu 와 같은 값인데 점표기법을 사용할 수 없어서 사용

  return (
    <div>
      <h1>사용자 프로필</h1>
      {profile ? (
        <div>
          <h2>{profile.name}</h2>
          <p>{profile.description}</p>
        </div>
      ) : (
        <p>존재하지 않는 프로필입니다.</p>
      )}
    </div>
  );
};

export default Profile;

useParams
<Route> 에서 넘어온 username 변수
<Link> 에서 넘어온 taechu 값, 두개를 객체형태로 반환해줍니다.
params = { username : "taechu" } 가 되겠죠.


쿼리스트링

쿼리스트링은 <Route> 컴포넌트를 따로 설정하지 않아도 됩니다.

쿼리스트링을 가져오는 두 가지 방법

import { useLocation } from "react-router-dom";

const location = useLocation();

useLocation : Location 객체를 반환합니다. 로케이션 객체의 search 속성은 URL 의 ? 구분자 뒤의 쿼리스트링을 가지고있습니다.

useLocation 같은 경우에 쿼리스트링을 따로 가공해서 써야하기때문에 잘 사용하지 않습니다.

리액트라우터의 useSearchParams Hook을 통해서 쿼리스트링을 간단하게 조작할 수 있습니다.

쿼리스트링은 <Link> , useSearchParams 만 신경쓰면 됩니다. <Route> 를 통해 어떤 변수를 받는지 설정이 필요없습니다.

Link

import { Link } from "react-router-dom";

const Home = () => {
  return (
    <div>
		<Link to="/path?id=xogud&name=kimtaehyung">링크</Link>
    </div>
  );
};

export default Home;

useSearchParams

import { useParams , useSearchParams } from "react-router-dom";

const Component = () => {
  const [searchParams, setsearchParams] = useSearchParams();
  const name = searchParams.get('name')
  
  return (
    <div>
		<div>{name}</div>
    </div>
  );
};

export default Component;

useSearchParams Hook은 구조분해 할당시 [searchParams,...] 첫번째 원소는 쿼리파라미터를 조회,수정 등 할 수 있는 메서드가 담긴 객체를 반환합니다.

[...,setSearchParams] 두번째 원소는 쿼리파라미터를 객체 형태로 업데이트할 수 있는 함수를 반환합니다.

useState와 비슷하죠?

<Route> 중첩하기

Route를 배울때 다중 중첩구조를 가질 수 있다고 하였죠? 그 방법에대해 이야기해보겠습니다.

//App.js
import { Route, Routes } from "react-router-dom";
import Home from "./pages/Home";
import Article from "./pages/Article"; //게시글 상세보기 페이지
import Articles from "./pages/Articles"; //게시글 목록 페이지

function App() {
  return (
    <Routes>
        <Route index element={<Home />}></Route> //가장 먼저 보여지는 페이지
        <Route path="/articles" element={<Articles />}> //라우트 중첩, id값 없으면 articles, 있으면 articles/:id
          <Route path=":id" element={<Article />}></Route> // path=/articles/:id 와 같이 해석됨. "/" 기호 빼도 됨.
        </Route>
    </Routes>
  );
}

export default App;
import { Link } from "react-router-dom";

const Home = () => {
  return (
    <div>
      <h1></h1>
      <p>가장먼저 보여지는 페이지 입니다.</p>
      <Link to="/articles">게시글 목록</Link> //게시글 목록 이동버튼
    </div>
  );
};

export default Home;
//Articles.js
import { Link, Outlet } from "react-router-dom";

const Articles = () => {
  return (
    <div>
      <ul>
        <li>
          <Link to="/articles/1">게시글1</Link>
        </li>
        <li>
          <Link to="/articles/2">게시글2</Link>
        </li>
      </ul>
      <Outlet /> //부모 컴포넌트에서 위치지정
    </div>
  );
};

export default Articles;
//Article.js
import { useParams } from "react-router-dom";

const Article = () => {
  const { id } = useParams();

  return (
    <div>
      <h2>게시글 {id}</h2>
    </div>
  );
};

export default Article;

위 코드는 Articles 컴포넌트에 Article 컴포넌트를 childeren 형식으로 전달했는데요. 이렇게되면 Articles 컴포넌트에서 자식 컴포넌트가 어디에 렌더링될지 위치를 정해주면 됩니다.

중첩된 라우트 기술은 페이지에 공통적으로 보여줘야할 헤드, 푸터, 공통레이아웃이 있을 때도 유용하게 사용됩니다.

//Header.js
//중첩으로 공통 레이아웃을 만들어보자.
import { Outlet } from "react-router-dom";

function Header() {
  return (
    <div>
    	<header><h1>헤더</h1><header/>
    	<Outlet/> //Route 컴포넌트에서 감싸고 있는 녀석들이 렌더링됨.
    <div>
    <Outlet/>
  );
}

export default Header;
//App.js
import { Route, Routes } from "react-router-dom";
import Home from "./pages/Home";
import Article from "./pages/Article"; //게시글 상세보기 페이지
import Articles from "./pages/Articles"; //게시글 목록 페이지
import Header from "./pages/Header"//헤더

function App() {
  return (
    <Routes>
      <Route element={<Header/>}>
      	<Route index element={<Home />}/> //가장 먼저 보여지는 페이지
        <Route path="/articles" element={<Articles />}> //라우트 중첩, id값 없으면 articles, 있으면 articles/:id
        <Route path=":id" element={<Article />}> // path=/articles/:id 와 같이 해석됨. "/" 기호 빼도 됨.
      </Route>
    </Routes>
  );
}

export default App;

위 코드에서 Home, Articles, Article 은 Header 에서 Outlet 으로 해석되고 Header컴포넌트에서 위치를 정해주어야 한다.

리액트 라우터 부가 기능

useNavigate
<Link> 컴포넌트의 대체 역할의 Hook 입니다.

사용방법은 아래와 같고, History 객체를 사용하는것같이 작동하네요.

import { useNavigate } from "react-router-dom";

const Layout = () => {
  const navi = useNavigate();
  //한페이지 이전으로
  const goBack = () => {
    navi(-1);
  };
  //특정페이지로 
  const goArticles = () => {
    navi("/acticles",{replace:true}); // 현재 페이지를 History 하지 않는 옵션
  };

  return (
    <div>
        <button onClick={goBack}>뒤로가기</button>
        <button onClick={goArticles}>게시글 목록</button>
    </div>
  );
};

export default Layout;

NavLink

import { NavLink } from "react-router-dom";

const Articles = () => {
  const activeStyle = {
    color: "blue",
    fontSize: "20px",
  };
  
  <NavLink to="/articles/1" style={({ isActive }) => (isActive ? activeStyle : undefined)} activeClassName="active-link" > 게시글1  </NavLink>

<NavLink> 컴포넌트는 링크에서 사용하는 경로가 ("/articles/1") 현재 페이지의 경로와 같으면 특정 스타일을 적용할 수 있습니다.
<NavLink> 컴포넌트의 style 은 {isActive:blooean} 객체를 파라미터로 받는 함수를 사용합니다.
<NavLink> 컴포넌트의 className 은 activeClassName="클래스명" 으로 사용하고 현재 페이지의 경로와 같은 페이지에 클래스명을 붙혀줍니다.

NotFound 페이지 만들기

사전에 정의되지않은 경로에 사용자가 진입했을 때 보여줄 페이지입니다.
App.js

import { Route, Routes } from "react-router-dom";
import Home from "./pages/Home";
import NotFound from "./pages/NotFound";
import Article from "./pages/Article"; //게시글 상세보기 페이지
import Articles from "./pages/Articles"; //게시글 목록 페이지
import Header from "./pages/Header"//헤더

function App() {
  return (
    <Routes>
      <Route element={<Header/>}>
      	<Route index element={<Home />}/> //가장 먼저 보여지는 페이지
        <Route path="/articles" element={<Articles />}/> //라우트 중첩, id값 없으면 articles, 있으면 articles/:id
        <Route path=":id" element={<Article />}/> // path=/articles/:id 와 같이 해석됨. "/" 기호 빼도 됨.
      
	<Route path="*" element={<NotFound/>}>
    </Routes>
  );
}

export default App;

<NotFound/> 컴포넌트를 라우팅하는 Route 컴포넌트에 path에 사용된 * 은 whildcard 라고합니다. 어떤 텍스트도 올 수 있다는 뜻입니다.
Routes 내부의 Route 의 경로를 모두 확인하고 일치하는 컴포넌트가 없다면 NotFound 페이지를 렌더링 합니다.

이 컴포넌트는 실행되면 리다이렉션 + 지정해놓은 경로로 페이지를 강제 이동시킵니다.
예를들어 사용자가 로그인상태가 아닌데 마이페이지에 접근하려합니다. 로그인 페이지로 리다이렉션 시켜야겠죠?

Login.js

const Login = () => {
  return <div>로그인 페이지 입니다. 로그인을 해주세요</div>;
};

export default Login;

MyPage.js

import { Navigate } from "react-router-dom";

const MyPage = () => {
  const isLoggedIn = false; //실제 프로젝트에선 true or false 로 값이 넘어옵니다.

  if (!isLoggedIn) {
    return <Navigate to="/login" replace="true" />; //경로:리다이렉션할 경로, 현재 페이지 History X 
  }
};

export default MyPage;

App.js

import { Route, Routes } from "react-router-dom";
import Login from "./pages/Login";
import MyPage from "./pages/MyPage";

function App() {
  return (
    <Routes>
      <Route path="/login" element={<Login />}></Route>
      <Route path="/mypage" element={<MyPage />}></Route>
    </Routes>
  );
}

export default App;

라우팅 포스팅은 여기까이 입니다.

profile
Front End

0개의 댓글