React Router은 React에서 웹 애플리케이션의 라우팅을 관리하기 위한 라이브러리입니다.
웹 페이지에서 라우팅이란 사용자가 서로 다른 경로나 URL을 방문할 때 해당 경로에 맞는 적절한 뷰 혹은 컴포넌트를 보여주는 기능을 의미합니다.
사용자가 웹 사이트의 '홈페이지', '서비스 소개', '연락처' 등 다양한 페이지를 방문할 때마다 적절한 화면을 보여주는 것이 라우팅이고, 이를 React에서 구현할 수 있도록 도와주는 것이 React Router입니다.
따라서, React Router는 복잡한 애플리케이션의 라우팅을 효과적으로 관리하고, 사용자 경험을 향상시키는 데 필요한 도구입니다.
1 ) BrowserRouter
웹 애플리케이션에서 가장 일반적으로 사용되는 라우터입니다. HTML5의 History API를 사용하여 UI를 업데이트합니다.
BrowserRouter
는 HTML5의 History API를 사용하여 클린한 URL을 제공합니다. 즉, URL에 해시(#)가 포함되지 않습니다. 이는 사용자에게 보다 친숙하고, SEO에도 유리합니다.
사용자가 직접 URL을 입력하거나 새로고침을 했을 때도 기대하는 페이지를 보여줄 수 있습니다.
BrowserRouter
를 사용할 때는 서버에서 모든 요청을 애플리케이션의 진입점으로 리다이렉트하는 설정이 필요합니다. 그렇지 않으면, 사용자가 직접 URL을 입력하거나 새로고침을 했을 때 404 에러가 발생할 수 있습니다.
2 ) HashRoter
Hash-based URL을 사용하는 라우터입니다.
서버 설정 없이 간편하게 사용할 수 있습니다. 해시(#) 부분은 브라우저만이 관리하므로, 서버는 항상 루트 페이지만을 제공하면 됩니다.
History API를 지원하지 않는 브라우저에서도 잘 동작합니다.
URL에 해시(#)가 포함되어 있습니다. 이는 URL이 다소 어색하게 보일 수 있으며, SEO에도 불리할 수 있습니다.
React를 위한 라우팅 라이브러리인
react-router
의 웹 버전이라고 할 수 있습니다. 웹 애플리케이션에서 사용되는 라우팅 컴포넌트들을 제공합니다.
1 ) 최상단 <BrowserRouter>
컴포넌트로 감싸기
react-router-dom에 내장되어 있는 BrowserRouter
라는 컴포넌트를 사용하여 감싸면 됩니다.
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>
);
2 ) , 컴포넌트로 감싸기
<Routes>
컴포넌트는 여러 Route를 감싸서 그 중 일치하는 라우트 단 하나만을 렌더링 시켜주는 역할을 합니다.
<Route>
는 path속성에 경로, element속성에는 컴포넌트를 넣어 줍니다.
<Route>
를 이용하여 중첩 라우팅을 할 수 있습니다.
<Route></Route>
사이에 하위 라우터를 넣으면됩니다. 하위 라우터의 컴포넌트는 상위 라우터에서 <Outlet/>
컴포넌트를 통해 가져올 수 있습니다.
부모 라우트의 경로와 정확히 일치하는 경로에서 렌더링되는 컴포넌트에는 ****index 속성을 넣어주면 됩니다. 즉, Main
컴포넌트는 URL이 /
일 때 렌더링 됩니다.
URL 파라미터별로 페이지를 동적으로 생성할 때에는 path을 /product/:productId 과같이 사용하며, :productId
부분은 실제로는 사용자가 입력하는 값으로 대체됩니다. productId 값은 Product 컴포넌트 내에서 useParam()
를 사용하여 가져올 수 있습니다.
어떤 경로와 일치하지않는 페이지를 설정할 때는 마지막 <Route>
의 path=""를 넣어서 설정합니다. 위의 라우터의 경로와 일치하는 라우터가 없는 경우 (모든 경로)를 NotFound로 처리한다는 의미입니다.
app.js
import React from 'react';
import { Route, Routes } from 'react-router-dom';
import Layout from './Layout';
import Main from './Main';
import Product from './Product';
function App() {
const [isLogin, setIsLogin] = useState(false);
const LoginContext = createContext();
return (
<Routes>
<Route path='/' element={<Layout />} >
<Route index element={<Main />} />
<Route path='/product/:productId' element={<Product />} />
<Route path="*" element={<NotFound />}></Route>
</Route>
</Routes>
);
}
export default App;
Layout.js
<Outlet/>
컴포넌트는 하위 라우터의 컴포넌트를 의미합니다.
아래 에서는 <header>
<footer>
사이에 Layout 컴포넌트 라우터의 하위 라우터 컴포넌트가 렌더링 됩니다.
<Link/>
: 사용자가 클릭하면 다른 위치로 이동하는 컴포넌트입니다. 페이지를 새로고침하지 않고 애플리케이션의 일부분만 업데이트합니다.
💡 <a>
태그와 <Link/>
컴포넌트 차이점
<a>
태그는 페이지를 이동시켜주는 역할을 합니다.
하지만 <a>
태그는 페이지를 새로고침하기 때문에 모든 리소스 들을 다시 불러오는 것과 같습니다.
<Link/>
컴포넌트는 이와 다르게 해당되는 페이지 이동시 해당 라우터로 이동시켜주어 해당 라우터에 해당되는 컴포넌트를 렌더링 시켜주는 방식으로 페이지를 새로고침 하지 않고 동적으로 컴포넌트를 불러옵니다.
import { useNavigate, Link, Outlet } from "react-router-dom";
const navStyles = {
display: "flex",
gap: "20px",
padding: "5px",
border: "1px solid black"
}
const footerStyles = {
fontSize: "30px",
fontWeight:"bold"
}
export default function Layout() {
const navigate = useNavigate();
const logout = () => {
navigate("/")
}
return (
<div>
<header>
<h1>Header</h1>
</header>
<nav style={navStyles}>
<Link to="/">Home</Link>
<Link to="/product/1">Prodcut1</Link>
<Link to="/product/2">Prodcut2</Link>
<Link to="/product/3">Prodcut3</Link>
</nav>
<Outlet />
<footer style={footerStyles}>footer</footer>
</div>
);
}
Main.js
function Main() {
return (
<div>
Main Contents
</div>
);
}
export default Main;
Product.js
productId
를 받아오기 위해 useParams()
을 사용하였습니다.
useParams()
: 현재 라우트의 URL 매개변수에 접근할 수 있게 해주는 훅입니다.
import { useParams } from "react-router-dom";
function Product() {
const { productId } = useParams();
return (
<div>
Product {productId} page.
</div>
);
}
export default Product;
NotFound.js
useNavigate()
: 라우팅을 프로그래밍 방식으로 제어할 수 있습니다. 즉, 사용자의 액션에 따라 페이지 이동을 제어하는 등의 작업을 수행할 때 유용하게 사용됩니다.
특정 이벤트가 실행됐을 때 동작하도록 하거나 추가적인 로직이 필요한 경우 useNavigate()
를 사용합니다.
아래 코드에서는 button를 클릭할 경우 Main( “/” )페이지로 이동되도록 하였습니다.
import { useNavigate } from "react-router-dom";
function NotFound() {
const navigate = useNavigate();
const goToMain = () => {
navigate("/");
}
return (
<div>
404 Not Found.
</div>
<button onClick={ goToMain }>Home</button>
);
}
export default Product
Login 유무에 따른 조건부 라우팅을 해보겠습니다.
Login 상태를 전역으로 관리하기 위해 ContextAPI를 사용하였습니다.
조건부 처리를 위해 삼항 연산자를 사용하였습니다.
조건에 만족하지 않는다면 <Navigate/>
컴포넌트를 통해 페이지를 이동시켜주었습니다.
<Navigate/>
: 페이지 네비게이션을 선언적으로 제어하는데 사용됩니다.
특정 조건에 따라 다른 경로로 리다이렉트하고 싶을 때 <Navigate/>
컴포넌트를 사용합니다.
to
속성을 통해 경로를 지정하면 해당 경로로 이동하게 됩니다.
replace
속성을 추가하면 해당 경로 이동을 replace로 이동하게 됩니다.
app.js
import { Navigate, Route, Routes } from "react-router-dom";
import Main from "./Main";
import Product from "./Product";
import Layout from "./Layout";
import { createContext, useState } from "react";
import Login from "./Login";
import NotFound from "./NotFound";
export const LoginContext = createContext({ isLogin: false });
export default function App() {
const [isLogin, setIsLogin] = useState(false);
return (
<LoginContext.Provider value={{ isLogin, setIsLogin }}>
<Routes>
{/* 로그인 이전 라우팅 처리 */}
<Route
path="/login"
element={!isLogin ? <Login /> : <Navigate to="/" replace />}
/>
{/* 로그인 이후 라우팅 처리 */}
{isLogin ? (
<Route path="/" element={<Layout />}>
<Route index element={<Main />} />
<Route path="product/:productId" element={<Product />} />
<Route path="*" element={<NotFound />} />
</Route>
) : (
<Route path="/*" element={<Navigate to="/login" replace />} />
)}
</Routes>
</LoginContext.Provider>
);
}
Login.js
import { useContext } from "react";
import { LoginContext } from "./App";
function Login() {
const { setIsLogin } = useContext(LoginContext);
const login = () => {
setIsLogin(true);
}
return <button onClick={login}>Login</button>;
}
export default Login;
Layout.js
기존과 코드와 동일하며 logout 버튼이 추가되었습니다.
import { useContext } from "react";
import { Link, Outlet } from "react-router-dom";
import { LoginContext } from "./App";
const navStyles = {
display: "flex",
gap: "20px",
padding: "5px",
border: "1px solid black",
};
const footerStyles = {
fontSize: "30px",
fontWeight: "bold",
};
export default function Layout() {
const { setIsLogin } = useContext(LoginContext);
const logout = () => {
setIsLogin(false);
};
return (
<div>
<header>
<h1>Header</h1>
</header>
<nav style={navStyles}>
<Link to="/">Home</Link>
<Link to="/product/1">Prodcut1</Link>
<Link to="/product/2">Prodcut2</Link>
<Link to="/product/3">Prodcut3</Link>
<button onClick={logout}>로그아웃</button>
</nav>
<Outlet />
<footer style={footerStyles}>footer</footer>
</div>
);
}