단의 개발 블로그

라우팅 본문

Web/React

라우팅

danso 2024. 11. 3. 17:11

라우팅

라우팅은 사용자가 요청한 URL에 따라 알맞은 페이지를 보여주는 것을 의미한다. 보통 애플리케이션은 여러 페이지로 구성되어 있는데, 페이지 별로 컴포넌트를 분리해서 관리할 때 필요하다. 리액트에서 사용되는 라우팅은 두가지 방식이 존재한다.

  • 리액트 라우터 : 라우팅 관련 라이브러리들 중에 가장 오래됐고, 많이 사용된다. 컴포넌트 기반으로 라우팅 시스템을 설정한다.
  • Next.js : 리액트 프로젝트의 프레임워크다. 프로젝트 설정 및 라우팅 시스템, 최적화, 다국어, 서버 사이드렌더링 등 다양한 기능을 제공한다. 리액트 라우터 대안으로 많이 사용된다.

SPA

Single page Application이란 하나의 페이지로 이루어진 애플리케이션이다. 멀티 페이지 애플리케이션은 사용자가 다른 페이지로 이동할 때마다 새로운 html을 받아 페이지를 로딩할 때마다 리소스를 전달 받아 보여준다. 사용자 인터렉션이 별로 없는 정적인 페이지에서는 해당 방식이 적합하지만 많은 경우 서버 자원을 낭비하게 된다. 리액트는 뷰 렌더링을 사용자 브라우저가 담당하도록 하고 필요한 부분만 업데이트 하는 방식을 사용하여 서버 측 자원을 효율적으로 사용할 수 있게 해준다. 

즉, html을 한번만 받아와 실행하고 이후에 필요한 데이터만 받아서 화면을 업데이트 하는 것을 SPA라고 한다.

 

라우터 설치 및 적용

yarn add react-router-dom

index.js를 변경한다. BrowserRouter라는 컴포넌트를 사용하여 감싸고, HTML5의 History API를 사용하여 페이지를 새로 불러오지 않고도 주소를 변경하고 현재 주소의 경로에 관련된 정보를 리액트 컴포넌트에서 사용할 수 있도록 해준다.

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
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();

src/pages 아래에 각각의 컴포넌트를 생성해준다.

const Home = () => {
    return (
        <div>
            <h1>홈</h1>
            <p>가장 먼저 나오는 페이지.</p>
        </div>
    )
}
export default Home;
const About = () => {
    return (
        <>
            <h1>About Page</h1>
            <p>리액트 라우터를 사용한 프로젝트</p>
        </>
    )
}

export default About;

사용자의 브라우저 주소 경로에 따라 원하는 컴포넌트를 보여주려면 Route라는 컴포넌트를 통해 라우트 설정을 해야한다.

App.js를 변경한다.

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

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

export default App;

 

Link 사용하기

위에서 만든 페이지는 주소를 직접 입력해야 이동할 수 있다. 보통의 웹 애플리케이션에서는 링크를 눌러서 이동하기 때문에 LInk 컴포넌트를 사용하여 다른 페이지로 이동하는 방법을 구현한다. a 태그를 사용할 경우 페이지를 이동할 때마다 새로 불러오기 때문에 해당 태그는 사용하지 않는다.

import React from 'react';
import {Link} from "react-router-dom";
const About = () => {
    return (
        <>
            <h1>About Page</h1>
            <p>리액트 라우터를 사용한 프로젝트</p>
            <Link to={"/"}>홈</Link>
        </>
    )
}

export default About;
import React from 'react';
import {Link} from "react-router-dom";
const Home = () => {
    return (
        <>
            <h1>홈</h1>
            <p>가장 먼저 나오는 페이지</p>
            <Link to={"/about"}>소개</Link>
        </>
    )
}
export default Home;

페이지 요청 시 파라미터와 쿼리 스트링을 사용하여 유동적인 값을 입력 받을 수도 있다.

  • 파라미터 : /user/1
  • 쿼리스트링: /user/list&page=1

유저 정보인 프로필 컴포넌트를 생성하고 아래와 같이 입력한다.

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

const data = {
    ds : {
        name: 'ds',
        description: '개발자'
    },
    gy : {
        name : 'gy',
        description: '소설가'
    }
}

const Profile = () => {
    const params = useParams();
    const profile = data[params.username];

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

export default Profile;

아래와 같이 코드를 수정한다.

import './App.css';
import React from 'react';
import {Route, Routes} from "react-router-dom";
import Home from "./pages/Home";
import About from "./pages/About";
import Profile from "./pages/Profile";

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

export default App;

 

http://localhost:3000/profile/ds 주소 요청시 입력했던 내용으로 데이터가 출력된다.

사용자가 이렇게 입력해서 요청하기엔 어려우니 Home에 link를 추가해서 이동할 수 있도록 한다.

import React from 'react';
import {Link} from "react-router-dom";
const Home = () => {
    return (
        <>
            <h1>홈</h1>
            <p>가장 먼저 나오는 페이지</p>
            <ul>
                <li>
                    <Link to={"/about"}>소개</Link>
                </li>
                <li>
                    <Link to="/profile/ds">ds의 프로필</Link>
                </li>
                <li>
                    <Link to="/profile/gy">gy의 프로필</Link>
                </li>
            </ul>
        </>
    )
}
export default Home;

쿼리스트링 사용은 아래와 같다. 주소에 ? 다음에 쿼리스트링을 입력하면 아래와 같이 쿼리스트링 내용이 출력된다. useLocation 훅은 아래 객체를 갖고 있다. 요청 예시 http://localhost:3000/about?queryString=abcdefg

  • pathname : 현재 주소 경로(쿼리 스트링 제외)
  • search : 맨 앞의 ? 문자를 포함한 쿼리스트링 값
  • hash : 주소의 # 문자열 뒤의 값 (History API가 지원되지 않는 구형 브라우저에서 클라이언트 라우팅을 사용할 때 쓰는 해시 라우터에서 사용)
  • state: 페이지로 이동할 때 임의로 넣을 수 있는 상태 값
  • key : location 객체의 고유 값, 초기에는 default 이며 페이지가 변경될 때 마다 고유 값이 설정됨.
import React from 'react';
import {Link, useLocation} from "react-router-dom";
const About = () => {
    const location = useLocation();
    return (
        <>
            <h1>About Page</h1>
            <p>리액트 라우터를 사용한 프로젝트</p>
            <p>쿼리 스트링: {location.search}</p>
            <Link to={"/"}>홈</Link>
        </>
    )
}

export default About;

 

쿼리스트링을 직접 파싱해서 사용하면 어렵기 때문에 searchParam 훅을 사용하여 해당 값을 얻어와서 사용한다. 배열 타입의 값을 반환하고, 첫번째 원소는 조회하거나 수정하는 메소드가 담긴 객체를 반환한다. get 메소드를 통해 특정 쿼리를 조회할 수 있고, set 메소드를 통해 특정 쿼리파라미털르 업데이트 할 수 있다. 쿼리 파라미터는 무조건 문자열 타입으로 넘어오기 때문에 따옴표를 꼭 붙여서 문자열 비교를 해야한다.

import React from 'react';
import {Link, useLocation, useSearchParams} from "react-router-dom";
const About = () => {
    const location = useLocation();
    const [searchParam, setSearchParams] = useSearchParams();
    const queryString = searchParam.get('queryString');
    const onToggle = () => {
        setSearchParams({queryString : queryString === 'abcd'?  true : false});
    }
    return (
        <>
            <h1>About Page</h1>
            <p>리액트 라우터를 사용한 프로젝트</p>
            <p>쿼리 스트링: {location.search}</p>
            <button onClick={onToggle}>Toggle</button>
            <Link to={"/"}>홈</Link>
        </>
    )
}

export default About;

 

중첩라우트

사용자 요청이 두개 이상 //으로 이루어진 요청이다. Articles와 Article 컴포넌트를 추가하고 App.js를 변경한다.

import './App.css';
import React from 'react';
import {Route, Routes} from "react-router-dom";
import Home from "./pages/Home";
import About from "./pages/About";
import Profile from "./pages/Profile";
import Articles from "./pages/Articles";
import Article from "./pages/Article";

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

            <Route path={"/articles"} element={<Articles/>} />
            <Route path={"/articles/:id"} element={<Article/>} />
        </Routes>
  );
}

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

const Articles = () => {
    return (
        <ul>
            <li>
                <Link to="/articles/1">게시글 1</Link>
            </li>
            <li>
                <Link to="/articles/2">게시글 2</Link>
            </li>
            <li>
                <Link to="/articles/3">게시글 3</Link>
            </li>
        </ul>
    )
}

export default Articles;
import {useParams} from "react-router-dom";

const Article = () => {
 const {id} = useParams();
 return (
     <div>
         <h1>게시글 {id}</h1>
     </div>
 )
}
export default Article;

보통의 게시글에는 게시글 + 다른 목록까지 출력하는 경우가 대부분이다. 보통의 방법이라면 게시글 컴포넌트에 리스트를 추가하지만, 중첩 라우트를 사용하면 보다 쉽게 해당 기능을 구현할 수 있다. App.js를 다음과 같이 수정한다.

<Route path={"/articles"} element={<Articles/>} >
    <Route path={":id"} element={<Article/>}/>
</Route>

Articles도 수정한다. Outlet 컴포넌트는 Route의 children으로 들어가는 JSX 엘리먼트를 보여주는 역할을 한다.

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

const Articles = () => {
    return (
        <>
            <Outlet />
                <ul>
                    <li>
                        <Link to="/articles/1">게시글 1</Link>
                    </li>
                    <li>
                        <Link to="/articles/2">게시글 2</Link>
                    </li>
                    <li>
                        <Link to="/articles/3">게시글 3</Link>
                    </li>
                </ul>
        </>
    )
}

export default Articles;

 

레이아웃 만들기

위에서 배운 내용을 종합하면, 반복적으로 사용되는 레이아웃을 만들어서 사용할 수 있다.

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

const Layout = () => {
    return (
        <div>
            <header style={{background: 'lightgrey', padding:16, fontSize: 24}}>
                Header
            </header>
            <main>
                <Outlet/>
            </main>
        </div>
    )
}

export default Layout;
function App() {
    return (
        <Routes>
            <Route element={<Layout/>}>
                <Route path="/" element={<Home/>}/>
                <Route path="/about" element={<About/>}/>
                <Route path="/profile/:username" element={<Profile/>}/>

                <Route path="/articles" element={<Articles/>}>
                    <Route path=":id" element={<Article/>}/>
                </Route>
            </Route>
        </Routes>
    );
}

export default App;

Route 컴포넌트는 index라는 props가 있다. path="/"와 동일한 의미를 가진다. 

<Routes>
    <Route path="/" element={<Layout/>}>
        <Route index element={<Home/>}/>
        <Route path="/about" element={<About/>}/>
        <Route path="/profile/:username" element={<Profile/>}/>

        <Route path="/articles" element={<Articles/>}>
            <Route path=":id" element={<Article/>}/>
        </Route>
    </Route>
</Routes>

 

부가기능

리액트 라우터는 유용한 API를 제공한다. 

useNavigate는 Link 컴포넌트를 사용하지 않고 다른페이지로 이동해야 하는 상황에 사용한다.

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

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

    const goBack = () => {
        navigate(-1);
    }
    const goArticles = () => {
        navigate('/articles');
    }
    return (
        <div>
            <header style={{background: 'lightgrey', padding:16, fontSize: 24}}>
                <button onClick={goBack}>뒤로가기</button>
                <button onClick={goArticles}>게시글 목록</button>
            </header>
            <main>
                <Outlet/>
            </main>
        </div>
    )
}

export default Layout;

NavLink 컴포넌트는 링크에서 사용하는 경로가 현재 라우트 경로와 일치하는 경우 특정 CSS를 적용하는 컴포넌트다.

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

const Articles = () => {
    const activeStyle = {
        color: 'green',
        fontSize: 21
    }
    return (
        <>
            <Outlet />
                <ul>
                    <li>
                        <NavLink to="/articles/1" style={({isActive}) => (isActive ? activeStyle : undefined)}>게시글 1</NavLink>
                    </li>
                    <li>
                        <NavLink to="/articles/2" style={({isActive}) => (isActive ? activeStyle : undefined)}>게시글 2</NavLink>
                    </li>
                    <li>
                        <NavLink to="/articles/3" style={({isActive}) => (isActive ? activeStyle : undefined)}>게시글 3</NavLink>
                    </li>
                </ul>
        </>
    )
}

export default Articles;

NotFound 만약 요청하는 페이지가 존재하지 않으면 에러를 발생시키지 않고, 404 페이지를 보여주도록 한다.

 

const NotFound = () => {
    return(
     <div
        style={{
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'center',
            fontSize: '2rem',
            position: 'absolute',
            width: '100%',
            height: '100%'
        }}
     >
         404
     </div>
    )
}

export default NotFound;
<Route path="*" element={<NotFound/>}/>

Navigate 컴포넌트는 화면에 보여주는 순간 다른페이지로 이동하고 싶을 때 사용하는 컴포넌트다. 페이지 리다이렉트 시 사용된다.

const Login = () => {
    return <>로그인</>
}
export default Login
import {Navigate} from "react-router-dom";

const MyPage = () => {
    const isLogin = false;
    if (!isLogin) {
        return <Navigate to={"/login"} replace={true} />;
    }
    return <>마이 페이지</>
}
export default MyPage;
<Route path="/login" element={<Login/>}/>
<Route path="/mypage" element={<MyPage />} />

/mypage 경로로 직접 입력하면 login 페이지로 이동한다.

 

 

 

리액트 라우터를 사용하면 다양한 경로에 따라 올바른 페이지를 보여줄 수 있다. 하지만 큰 규모의 프로젝트 일 경우 자원 낭비가 심해진다. 중첩된 라우트 안에 있는 모든 컴포넌트가 한번에 불러와지기 때문이다. 컴포넌트 요청 시점에 페이지를 불러오는 코드 스플리팅 기술로 해결할 수 있다.

'Web > React' 카테고리의 다른 글

리덕스 미들웨어  (0) 2024.11.05
리덕스  (0) 2024.11.04
Hooks  (0) 2024.11.01
이벤트  (0) 2024.11.01
컴포넌트  (0) 2024.11.01