INTRO

안녕하세요, 오늘은 리액트 라우터에 대해 알아보았습니다. 라우터란 무엇인지, 어떻게 사용하는지 정리했습니다.

1. 리액트 라우터

웹 브라우저에 여러 컴포넌트가 있을 때, 특정 조건에 맞춰서 필요로 하는 것만 보이도록 조절한다. (Restful API)

- 사용

프로젝트 생성 및 라이브러리 설치
c:\react> npx create-react-app router-app
c:\react> cd router-app
c:\react\router-app> npm install react@18 react-dom@18
c:\react\router-app> npm install web-vitals
c:\react\router-app> npm install react-router-dom
c:\react\router-app> code .
c:\react\router-app> npm start

- 프로젝트에 라우터를 적용

BrowserRouter 컴포넌트 이용

  • React Router v6 이전부터 사용되어 온 방식으로, 선언적으로 간단한 설정을 제공
  • HTML5의 History API를 기반으로 URL 관리
  • 애플리케이션의 루트 컴포넌트로 설정하며, 내부에서 \, \를 통해 라우팅 구조를 정의
  • 작은 규모의 어플리케이션에 적합
  • 라우트 데이터를 명시적으로 로드하거나 관리하는 기능이 제한 (특정 컴포넌트가 보일 때 필요로 하는 데이터를 제공해주지 X)
import {BrowserRouter, Router, Route } from "react-router-dom";

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

RouterProvider 이용 (권장)

  • React Router v6.4 이상에서 도입된 방식으로, 라우팅을 더 유연하고 동적으로 설정할 수 있도록 설계
  • createBrowserRouter와 함께 사용되며, 데이터 로딩, 에러 핸들링, 동적 라우팅 설정 등이 포함된 라우트 객체 기반의 라우팅을 제공
  • 라우팅 설정이 객체 기반으로 변경되며, 데이터 로드와 에러 처리 기능이 포함
const router = createBrowserRouter([
	{
    	path: "/",
        element: <Home />,
        loader: async () => { 
        	const data = await fetchDataFromHome(); 
            return data;
           },
        errorElement: <ErrorPage />
    },
    {
    	path: "/About",
        element: <About />,
    },
    {
    	path: "/Contact",
        element: <Contact />,
    }
]);

function App(){
	return <RouterProvider router={router } />
}

- 라우팅할 페이지 컴포넌트 생성

Home.js

export default function Home() {
  return (
    <div>
      <h1>Home</h1>
      <h2>가장 먼저 보이는 페이지</h2>
    </div>
  );
}

About.js

export default function About() { // export defaulta -> export default
  return (
    <div>
      <h1>About</h1>
      <h2>리액트 라우트 연습</h2>
    </div>
  );
}

App.js

App.js 파일에 react-router-dom에 내장되어 있는 BrowserRouter 컴포넌트를 추가.
Route 컴포넌트로 특정 경로에 원하는 컴포넌트 보여주기 (주소 패턴에 따라 다른 컴포넌트를 제공)

import { BrowserRouter, Link, Route, Routes } from "react-router-dom";
import Home from "./Home";
import About from "./About";

function App() {
  return (
    <BrowserRouter>
    <ul>
      <li><Link to="/">Home</Link></li>
      <li><Link to="/about">About</Link></li>
      <li><Link to="/info">Info</Link></li>
    </ul>

      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/info" element={<About />} />
      </Routes>
    </BrowserRouter>
  );
}

export default App;

- 브라우저 주소창에 아래 주소를 입력해서 컴포넌트 실행을 확인

http://localhost:3000/ ⇒ Home 컴포넌트가 제공
http://localhost:3000/about ⇒ About 컴포넌트가 제공

클릭하면 다른 주소로 이동시켜주는 컴포넌트. (a 태그와 같은 기능을 하지만 a는 서버에 리퀘스트 발생시킨다(서브밋 발생 == 렌더링 발생 -> 상태변수 초기화 되어버림 -> 리액트에서 쓰지 말자!)
HTML5 Histroy API를 사용해 브라우저의 주소만 바꿀 뿐 페이지를 새로 불러오지는 않음.

\링크 이름\

<BrowserRouter>
    <ul>
      <li><Link to="/">Home</Link></li>
      <li><Link to="/about">About</Link></li>
    </ul>

      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
      </Routes>
</BrowserRouter>

3. 같은 컴포넌트에 다른 경로 설정

<li><Link to="/info">정보</Link></li>

<Route path="/info" element={<About />} />

4. 가변 데이터를 컴포넌트로 전달하는 방법

  • 파라미터 (parameter)

    /profile/honggildong

  • 쿼리 문자열 (query String)

    /profile?name=honggildong

- 파라미터를 이용한 가변 데이터 처리

  • 파라미터 이름은 라우트를 설정할 때 Route 컴포넌트의 path props에 :파라미터이름 형식으로 설정
  • 파라미터 조회(참조)는 useParams 훅 함수를 이용해 객체 형태로 조회

profile.js

⇒ 파라미터로 전달된 사용자 식별자와 일치하는 사람의 정보를 출력

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

const users = {
    mrgo: {
        name: "고길동",
        desc: "둘리를 싫어하는 자"
    },
    mrhong: {
        name: "홍길동",
        desc: "호부호형을 원하는 자"
    }
};

// http://localhost:3000/profile/mrgo
//                               ~~~~ 
//                               userid 변수 이름으로 전달
// 주소에 포함된 사용자 식별자(여기에서는 userid)에 해당하는 사용자 정보를 출력
export default function Profile() {
    // 주소에 포함된 파라미터를 추출 
    const params = useParams();

    // 파라미터에서 userid의 값을 추출해서 
    // 해당 값을 이용해서 users 객체에서 일치하는 사용자 정보를 추출
    const profile = users[params.userid];
    return (
        <>
            {
                profile ? (
                    <>
                        <h1>{profile.name}</h1>
                        <h2>{profile.desc}</h2>
                    </>
                ) : (
                    <h1>일치하는 사용자가 없습니다.</h1>
                )
            }
        </>
    );
}

App.js

Profile 컴포넌트를 호출하는 링크와 Route를 추가

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

function App() {
  return (
    <BrowserRouter>
      <ul>
        <li><Link to="/profile/mrgo">고길동 프로파일</Link></li>
        <li><Link to="/profile/mrhong">홍길동 프로파일</Link></li>
        <li><Link to="/profile/none">없는 프로파일</Link></li>
      </ul>

      <Routes>
        <Route path="/profile/:userid" element={<Profile />} />
      </Routes>
    </BrowserRouter>
  );
}

export default App;

- 쿼리 스트링을 이용한 가변 데이터 처리

...?query_name=query_value?query_name2=query_value2 ~~~ 의 형태로 전달

  • 쿼리 스트링은 Route 컴포넌트에 추가 설정 없이 사용 가능
  • 쿼리 스트링은 useLocation 훅 함수가 반환하는 location 객체의 search 항목을 활용하여 추출

useLocation 훅 함수

location 객체를 반환한다.

  • pathname : 현재 주소의 경로 (쿼리 스트링 제외)
  • search : ? 문자를 포함한 쿼리 스트링 값
  • hash : 주소의 # 문자열 뒤의 값
  • state : 페이지로 이동할 때 임의로 넣을 수 있는 상태 값
  • key : location 객체의 고유 값
>>> About 컴포넌트 수정

location.search 값 중 detail 값이 true인 경우 추가 정보가 출력되도록 수정.
쿼리스트링으로 전달되는 모든 값은 문자열 타입을 가진다.


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

export default function About() {
   // 쿼리 스트링을 추출 ==> ?aaaa=aaaa&bbbb=bbbb&cccc=cccc
   const location = useLocation();
   const quries = qs.parse(location.search, { ignoreQueryPrefix: true });
   console.log(quries);

   // http://localhost:3000/about?detail=true&name=hong&age=23 형태로 요청을 하면
   // {detail: 'true', name: 'hong', age: '23'}
   //          ~~~~~~        ~~~~~~       ~~~~ <== 쿼리 스트링으로 전달되는 모든 값은 문자열 타입을 가짐

   return (
       <div>
           <h1>About</h1>
           <h2>리액트 라우트 연습</h2>
           {
               quries.detail === "true" && <h2>상세 내역입니다.</h2>
           }
       </div>
   );
}

useSearchParams 훅 함수

  • 리액트 라우터 v6부터는 쿼리 스트링을 쉽게 조작할 수 있도록 useSearchParams 훅 함수를 제공

>>> qs 라이브러리

쿼리 스트링 파싱을 도와주는 라이브러리

c:\react\router-app> npm install qs

사용하는 방법

>>> About 컴포넌트 수정

useSearchParams 훅을 사용하도록 About 컴포넌트를 수정.
파싱하는 과정이 필요 X.

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

export default function About() {
    /*
    const location = useLocation();
    const quries = qs.parse(location.search, { ignoreQueryPrefix: true });
    */

    const [searchParams, setSearchParams] = useSearchParams();
    const detail = searchParams.get("detail");

    return (
        <div>
            <h1>About</h1>
            <h2>리액트 라우트 연습</h2>
            {
                detail === "true" && <h2>상세 내역입니다.</h2>
            }
        </div>
    );
}

5. 서브 라우트 (중첩 라우트)

복잡한 애플리케이션의 URL과 화면 계층을 효과적으로 관리.
Outlet과 childern 속성을 활용해 계층 구조를 반영한 라우팅을 설정.
특정 라우트의 하위 경로로 구성된 라우트
부모 라우트의 레이아웃을 공유하면서 자식 컴포넌트를 렌더링

- Profiles.js

프로파일 컴포넌트로의 링크(Link)와 라우팅 결과를 출력할 Outlet을 포함하는 컴포넌트.
Outlet: Route의 children으로 들어오는 JSX 엘리멘트를 보여주는 역할

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

export default function Profiles() {
    return (
        <>
            <h1>사용자 목록</h1>
            <ul>
                <li><Link to="/profiles/mrgo">고길동 프로파일</Link></li>
                <li><Link to="/profiles/mrhong">홍길동 프로파일</Link></li>
                <li><Link to="/profiles/none">없는 프로파일</Link></li>
            </ul>
            <hr />
            <Outlet />
        </>
    );
}

- App.js

Profile 컴포넌트와 관련된 Link와 Route를 제거하고, Profiles 컴포넌트와 관련된 Link와 Route를 추가.

import { BrowserRouter, Link, Route, Routes } from "react-router-dom";
import Home from "./Home";
import About from "./About";
import Profiles from "./Profiles";

function App() {
  return (
    <BrowserRouter>
      <ul>
        <li><Link to="/"></Link></li>
        <li><Link to="/about">소개</Link></li>
        <li><Link to="/info">정보</Link></li>
        {/*
        <li><Link to="/profile/mrgo">고길동 프로파일</Link></li>
        <li><Link to="/profile/mrhong">홍길동 프로파일</Link></li>
        <li><Link to="/profile/none">없는 프로파일</Link></li>
        */}
        <li><Link to="/profiles">프로파일</Link></li>
      </ul>

      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/info" element={<About />} />
        {/*
        <Route path="/profile/:userid" element={<Profile />} />
        */}
        <Route path="/profiles" element={<Profiles />}>
          <Route path=":userid" element={<Profile />} />
        </Route>
      </Routes>
    </BrowserRouter>
  );
}

export default App;

6. 공통 레이아웃 컴포넌트

중첩된 라우트와 Outlet 컴포넌트를 이용해 각 페이지(컴포넌트)에서 공통적으로 보여줘야 하는 레이아웃을 처리할 때 유용.

- Layout.js

메뉴를 \

태그를 포함하고, 각 메뉴를 클릭했을 때 나타낼 컴포넌트는 \ 태그에 출력.

- App.js

메뉴를 제거하고, Layout 컴포넌트로 각 페이지 컴포넌트를 둘러싼다.

import { BrowserRouter, Link, Route, Routes } from "react-router-dom";
import Home from "./Home";
import About from "./About";
import Profiles from "./Profiles";
import Profile from "./Profile";
import Layout from "./Layout";

function App() {
  return (
    <BrowserRouter>

      <Routes>
        <Route element={<Layout />} >
            <Route path="/" element={<Home />} />
            <Route path="/about" element={<About />} />
            <Route path="/info" element={<About />} />

            <Route path="/profiles" element={<Profiles />}>
              <Route path=":userid" element={<Profile />} />
            </Route>
         </Route>
      </Routes>
    </BrowserRouter>
  );
}

export default App;

- Layout.css

header{
    background-color: #ccc;
    padding: 16px;
    font-size: 32px;
}
ul{
    list-style-type: none;
    margin: 0;
    padding: 0;
}
li{
    display: inline;
    padding: 0 20px;
}

8. 리액트 라우트의 부가 기능

- useNavigate 훅

Link 컴포넌트를 사용하지 않고 페이지를 이동할 때 사용하는 훅 함수

const navigate = useNavigate();

Layout.js

이전 페이지로 이동 버튼, 정보 페이지로 이동 버튼 추가

import { Link, Outlet, useNavigate } from "react-router-dom";
import "./Layout.css";

export default function Layout() {
    const navigate = useNavigate();

    return (
        <div>
            <header>
                <ul>
                    <li><Link to="/"></Link></li>
                    <li><Link to="/about">소개</Link></li>
                    <li><Link to="/info">정보</Link></li>
                    <li><Link to="/profiles">프로파일</Link></li>
                </ul>
                <button onClick={() => navigate(-1)}>이전 페이지로 이동</button>
                <button onClick={() => navigate("/info")}>정보 페이지로 이동</button>
            </header>
            <main>
                <Outlet />
            </main>
        </div>
    );
}

링크에서 사용하는 경로가 현재 라우트의 경로와 일치하는 경우 특정 스타일 또는 CSS 클래스를 적용하는 컴포넌트.
이 컴포넌트를 사용하면 style 또는 className을 설정할 때 { isActive: boolean }을 매개변수로 전달받는 함수를 정의할 수 있음.

Layout.js

소개 메뉴를 클릭하면 글자색을 붉은색으로 설정

import { Outlet, Link, useNavigate, NavLink } from "react-router-dom";
import "./Layout.css";

export default function Layout() {
    const navigate = useNavigate();

    return (
        <div>
            <header>
                <ul>
                    <li><Link to="/"></Link></li>
                    <li><NavLink to="/about" style={
                        ({ isActive }) => isActive ? { color: "red" } : undefined
                    }>소개</NavLink></li>
                    <li><Link to="/info">정보</Link></li>
                    <li><Link to="/profiles">프로파일</Link></li>
                </ul>
                <button onClick={() => navigate(-1)}>이전 페이지로 이동</button>
                <button onClick={() => navigate("/info")}>정보 페이지로 이동</button>
            </header>
            <main>
                <Outlet />
            </main>
        </div>
    );
};

- NotFound 페이지

Route 컴포넌트의 path props의 값으로 *를 사용하면 아무 텍스트나 매칭한다는 뜻이 되며, 라우트 엘리먼트의 상단에 위치하는 라우트의 규칙을 모두 확인하고, 일치하는 라우트가 없다면 이 라우트가 화면에 나타나게 됨.

NotFound.js

export default () => {
    return (
        <div style={{
            display: "flex",
            alignItems: "center",
            justifyContent: "center",
            fontSize: 64,
            position: "absolute",
            width: "100%",
            height: "100%",
        }}>404 Not Found</div>
    );
}

App.js

NotFound 컴포넌트를 라우트로 등록

import { BrowserRouter, Link, Route, Routes } from "react-router-dom";
import Home from "./Home";
import About from "./About";
import Profiles from "./Profiles";
import Profile from "./Profile";
import Layout from "./Layout";
import NotFound from "./NotFound";

function App() {
  return (
    <BrowserRouter>

      <Routes>
        <Route element={<Layout />} >
            <Route path="/" element={<Home />} />
            <Route path="/about" element={<About />} />
            <Route path="/info" element={<About />} />

            <Route path="/profiles" element={<Profiles />}>
              <Route path=":userid" element={<Profile />} />
            </Route>
         </Route>
         <Route path="*" element={<NotFound/>} />
      </Routes>
    </BrowserRouter>
  );
}

export default App;

- Navigate 컴포넌트

컴포넌트를 화면에 보여주는 순간 다른 컴포넌트(페이지)로 이동할 때 사용.
페이지를 리다이렉트할 때 사용.

Login.js

export default () => <h1>로그인 페이지</h1>;

MyPage.js

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

export default () => {
    if(!isLoggedIn){
     return <Navigate to = "/login" replace={true}/>   
    }
    return <h1>마이 페이지 </h1>;
}

App.js

위의 라우트 정보 추가.

import { BrowserRouter, Route, Routes } from "react-router-dom";
import Home from "./Home";
import About from "./About";
import Profiles from "./Profiles";
import Profile from "./Profile";
import Layout from "./Layout";
import NotFound from "./NotFound";
import MyPage from "./MyPage";
import Login from "./Login";

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route element={<Layout />}>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
          <Route path="/info" element={<About />} />
          <Route path="/profiles" element={<Profiles />}>
            <Route path=":userid" element={<Profile />} />
          </Route>
          <Route path="/login" element={<Login />} />
          <Route path="/mypage" element={<MyPage />} />
        </Route>
        <Route path="*" element={<NotFound />} />
      </Routes>
    </BrowserRouter>
  );
}

export default App;

Layout.js

마이페이지 메뉴 추가

import { Outlet, Link, useNavigate, NavLink } from "react-router-dom";
import "./Layout.css";

export default function Layout() {
    const navigate = useNavigate();

    return (
        <div>
            <header>
                <ul>
                    <li><Link to="/"></Link></li>
                    <li><NavLink to="/about" style={
                        ({ isActive }) => isActive ? { color: "red" } : undefined
                    }>소개</NavLink></li>
                    <li><Link to="/info">정보</Link></li>
                    <li><Link to="/profiles">프로파일</Link></li>
                    <li><Link to="/mypage">마이페이지</Link></li>
                </ul>
                <button onClick={() => navigate(-1)}>이전 페이지로 이동</button>
                <button onClick={() => navigate("/info")}>정보 페이지로 이동</button>
            </header>
            <main>
                <Outlet />
            </main>
        </div>
    );
};

테스트

마이페이지 메뉴를 클릭하면 로그인 페이지로 이동하는 것을 확인

일반적으로 replace = { true }로 둔다. (계속해서 같은 페이지가 보여지는 걸 방지)

- RouterProvider 사용

App.js

RouterProvider 추가, createBrowserRouter 함수에 인자 값으로 라우팅 정보를 전달

import { createBrowserRouter, RouterProvider } from "react-router-dom";
import Home from "./Home";
import About from "./About";
import Profiles from "./Profiles";
import Profile from "./Profile";
import Layout from "./Layout";
import NotFound from "./NotFound";
import MyPage from "./MyPage";
import Login from "./Login";

const router = createBrowserRouter([
  {
    path: "/", element: <Layout />, children: [
      { path: "/", element: <Home /> },
      { path: "/about", element: <About /> },
      { path: "/info", element: <About /> },
      {
        path: "/profiles", element: <Profiles />, children: [
          { path: ":userid", element: <Profile /> }
        ]
      },
      { path: "/login", element: <Login /> },
      { path: "/mypage", element: <MyPage /> }
    ]
  },
  { path: "*", element: <NotFound /> }
]);

function App() {
  return <RouterProvider router={router} />;
}

export default App;

OUTRO

profile
지니니

0개의 댓글