안녕하세요! delay100입니다. 이번 포스팅에서는 Router(라우터) & SPA(Single Page Application)에 대해 공부해봅시다.
대부분의 설명은 주석으로 달아놓았으니 코드에 대한 설명은 주석을 확인해주세요!
책 리액트를 다루는 기술, 개정판의 13장 내용을 다루고 있습니다.
이번 포스팅의 Github 링크
https://github.com/delay-100/study-react/tree/main/ch13/router-tutorial
1. Routing & SPA
1-1. Routing
웹 애플리케이션에서..
라우팅?
- 사용자가 요청한 URL에 따라 알맞은 페이지를 보여주는 것
- 여러 페이지로 구성된 웹 애플리케이션을 만들 때 페이지 별로 컴포넌트들을 분리해가면서 프로젝트를 관리하기 위해 필요한 것
리액트에서 라우트 시스템을 구축하기 위해 사용할 수 있는 2가지 선택지
- 리액트 라우터(React Router): 가장 많이 사용되는 리액트의 라우팅 관련 라이브러리입니다. 컴포넌트 기반으로 라우팅 시스템을 설정할 수 있습니다.
- Next.js: 리액트 프로젝트의 프레임워크이며 파일 경로 기반으로 작동합니다. 다양한 기능(리액트 프로젝트 설정을 하는 기능, 라우팅 시스템, 최적화, 다국어 시스템 지원, 서버사이드 렌더링 등)을 제공합니다.
앞으로는 리액트 라우터를 이용할 것입니다.
1-2. SPA
SPA?
- Single Page Application
- 하나의 페이지로 이루어진 애플리케이션
- html은 한 번만 받아와서 웹 애플리케이션을 실행시킨 후, 이후에는 필요한 데이터만 받아와서 화면에 업데이트 하는 것
- 기술적으로 한 페이지만 존재하지만 사용자가 경험하기에는 여러 페이지가 존재하는 것처럼 느낄 수 있음
- 사용자 인터랙션이 많고 다양한 정보를 제공하는 모던 웹 애플리케이션에 적합
- 뷰 렌더링을 사용자의 브라우저가 담당하도록 함
- 웹 애플리케이션을 브라우저에 불러와서 실행시킨 후에 사용자와의 인터렉션이 발생하면 필요한 부분만 자바스크립트를 사용해 업데이트를 하는 방식
- 새로운 데이터가 필요하다면 서버 API를 호출하여 필요한 데이터만 새로 불러와 애플리케이션에서 사용할 수 있게 해줌
VS
MPA?
- Multi Page Application
- 사용자가 다른 페이지로 이동할 때마다 새로운 html을 받아옴
- 사용자 인터렉션이 별로 없는 정적인 페이지들에 적합
- 페이지를 로딩할 때마다 서버에서 CSS, JS, 이미지 파일 등의 리소스를 전달받아 브라우저 화면에 보여줌
- 각 페이지마다 다른 html을 만들어 제공하거나 데이터에 따라 유동적인 html을 생성해주는 템플릿을 사용하기도 함
1-3. 리액트의 라우팅
리액트의 라우터 같은 라우팅 시스템은 사용자의 브라우저 주소창의 경로에 따라 알맞은 페이지를 보여줍니다.
이후 링크를 눌러서 다른 페이지로 이동할 때는 서버에 다른 페이지의 html을 새로 요청하지 않고,
브라우저의 History API를 사용하여 브라우저의 주소창의 값만 변경하고 기존에 페이지에 띄웠던 웹 애플리케이션을 그대로 유지하면서 라우팅 설정에 따라 또 다른 페이지를 보여줍니다.
2. 간단한 SPA 예시
개념을 익혔으니 리액트 라우터로 SPA를 만들어볼까요?
2-1. 프로젝트 준비하기
프로젝트 생성 명령어
yarn create react-app router-tutorial
cd router-tutorial
설치할 라이브러리
yarn add react-router-dom
2-2. 리액트 라우팅 기초
프로젝트에 리액트 라우터를 적용 시, src/index.js 파일에서 react-router-dom에 내장되어있는 BrowserRouter라는 컴포넌트를 사용하여 감싸면 됩니다.
- BrowserRouter 컴포넌트 적용 - src/index.js
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"; // BrowserRouter 컴포넌트: 웹 애플리케이션에 HTML5의 History API를 사용하여 페이지를 새로 불러오지 않고도 주소를 변경하고 현재 주소의 경로에 관련된 정보를 리액트 컴포넌트에서 사용할 수 있도록 해 줌
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
단순히 페이지를 위한 컴포넌트들을 다른 파일들과 구분하기 위해 pages라는 폴더를 만들었습니다. (그냥 src에 작업해도 상관 없습니다.)
- Home 페이지 컴포넌트 - src/pages/Home.js
// Home 페이지 컴포넌트: 사용자가 웹 사이트에 들어왔을 때 가장 먼저 보여지게 됨
import { Link } from "react-router-dom";
const Home = () => {
return (
<div>
<h1>홈</h1>
<p>가장 먼저 보여지는 페이지입니다.</p>
<Link to="/about">소개</Link>
{/* 리액트 라우터를 사용하는 프로젝트에서는 a태그 대신 Link를 대신 사용, Link역시 a를 사용하긴 하지만 페이지를 새로 불러오는 것을 막고 History API를 통해 브라우저 주소의 경로만 바꾸는 기능이 내장되어 있음 */}
</div>
);
};
export default Home;
- About 페이지 컴포넌트 - src/About.js
// About 페이지 컴포넌트: 웹 사이트를 소개
const About = () => {
return (
<div>
<h1>소개</h1>
<p>리액트 라우터를 사용해 보는 프로젝트입니다.</p>
</div>
);
};
export default About;
- Home & About 페이지 컴포넌트 연결하기 - src/App.js
import { Route, Routes } from "react-router-dom";
import About from "./pages/About";
import Home from "./pages/Home";
const App = () => {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
);
};
export default App;
2-3. URL 파라미터 라우팅
URL 파라미터?
- 주소의 경로에 유동적인 값을 넣는 형태
- 주로 ID 또는 이름을 사용하여 특정 데이터를 조회할 때 사용
URL 파라미터 예시
/profile/velopert
- URL 파라미터 라우팅 예시 - src/pages/Profile.js
import { useParams } from "react-router-dom";
const data = {
// data 객체에 예시 프로필 정보들을 key-value 형태로 담아둠
delay100: {
name: "백지연",
description: "리액트를 좋아하는 개발자",
},
gildong: {
name: "홍길동",
description: "고전 소설 홍길동전의 주인공",
},
};
const Profile = () => {
// URL 파라미터는 useParams라는 Hook을 사용하여 객체 형태로 조회할 수 있습니다.
// URL 파라미터 이름은 라우트 설정을 할 때 Route 컴포넌트의 path props를 통해 설정합니다.
const params = useParams();
const profile = data[params.username];
// username URL 파라미터를 통해 프로필을 조회한 뒤에
return (
<div>
<h1>사용자 프로필</h1>
{/* 프로필이 존재하면 프로필 정보를 보여줌, 프로필이 존재하지 않으면 '존재하지 않는 프로필입니다'라는 문구를 보여줌 */}
{profile ? (
<div>
<h2>{profile.name}</h2>
<p>{profile.description}</p>
</div>
) : (
<p>존재하지 않는 프로필입니다.</p>
)}
</div>
);
};
export default Profile;
- URL 파라미터 라우팅 예시 - src/App.js
import { Route, Routes } from "react-router-dom";
import About from "./pages/About";
import Home from "./pages/Home";
import Profile from "./pages/Profile";
const App = () => {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/profiles/:username" element={<Profile />} />
</Routes>
);
};
export default App;
- URL 파라미터 라우팅 예시 - src/pages/Home.js
// Home 페이지 컴포넌트: 사용자가 웹 사이트에 들어왔을 때 가장 먼저 보여지게 됨
import { Link } from "react-router-dom";
const Home = () => {
return (
<div>
<h1>홈</h1>
<p>가장 먼저 보여지는 페이지입니다.</p>
<ul>
<li>
<Link to="/about">소개</Link>
{/* 리액트 라우터를 사용하는 프로젝트에서는 a태그 대신 Link를 대신 사용, Link역시 a를 사용하긴 하지만 페이지를 새로 불러오는 것을 막고 History API를 통해 브라우저 주소의 경로만 바꾸는 기능이 내장되어 있음 */}
</li>
<li>
<Link to="/profiles/delay100">delay100의 프로필</Link>
</li>
<li>
<Link to="/profiles/gildong">gildong의 프로필</Link>
</li>
<li>
<Link to="/profiles/temp">존재하지 않는 프로필</Link>
</li>
</ul>
</div>
);
};
export default Home;
2-4. 쿼리스트링 라우팅
쿼리스트링을 사용할 때는 URL 파라미터와 달리 Route 컴포넌트를 사용할 때 별도로 설정해야 하는 것이 없습니다.
쿼리스트링?
- 주소의 뒷부분에 ? 문자열 이후에 key=value로 값을 정의하며 &로 구분하는 형태
- 키워드 검색, 페이지네이션, 정렬 방식 등 데이터 조회에 필요한 옵션을 전달할 때 사용
쿼리스트링 예시
/articles?page=1&keyword=react
첫 번째 예시를 보기전에, 알아야 할 Hook이 있습니다. 바로 useLocation입니다.
useLocation?
- location 객체를 반환하는데, 이 객체는 현재 사용자가 보고 있는 페이지의 정보를 가지고 있음
- 쿼리스트링은 location.search 값을 통해 조회할 수 있음
[이 객체에 있는 값들]
- pathname: 현재 주소의 경로(쿼리스트링 제외)
- search: 맨 앞의 ? 문자를 포함한 쿼리스트링 값
- hash: 주소의 #문자열 뒤의 값 (주로 History API가 지원되지 않는 구형 브라우저에서 클라이언트 라우팅을 사용할 때 쓰는 해시 라우팅에서 사용)
- state: 페이지로 이동할 때 임의로 넣을 수 있는 상태 값
- key: location 객체의 고유값, 초기에는 default이며 페이지가 변경될 때마다 고유의 값이 생성됨
- 쿼리스트링 라우팅 예시1 - src/pages/About.js
// About 페이지 컴포넌트: 웹 사이트를 소개
import { useLocation } from 'react-router-dom';
const About = () => {
const location = useLocation(); // useLocation이라는 Hook 사용: 이 Hook은 location 객체를 반환하는데, 현재 사용자가 보고 있는 페이지의 정보를 지니고 있음
return (
<div>
<h1>소개</h1>
<p>리액트 라우터를 사용해 보는 프로젝트입니다.</p>
<p>쿼리스트링: {location.search}</p> {/* 쿼리스트링은 location.search 값을 통해 조회가능*/}
</div>
);
};
export default About;
서버를 키고, 직접 주소창에 http://localhost:3000/about?detail=true&mode=1를 입력해주었습니다.
쿼리스트링 값이 ?detail=true&mode=1로 정확히 나오고 있네요!
이제 위 문자열에서 앞에 있는 ?를 지우고 &로 분리한 뒤 key와 value를 파싱하는 작업을 해야합니다. 이런 작업은 npm에서 qs또는 querystring 패키지를 설치해 처리 가능합니다.
그러나 리액트 라우터에서는 v6부터 useSearchParams라는 Hook을 이용해 쿼리스트링을 더욱 쉽게 파싱할 수 있습니다.
useSearchParams?
- 배열 타입의 값을 반환하는데, 이 객체는 현재 사용자가 보고 있는 페이지의 정보를 가지고 있음
- 쿼리스트링은 location.search 값을 통해 조회할 수 있음
- 첫 번쨰 원소: 쿼리파라미터를 조회하거나 수정하는 메서드들이 담긴 객체를 반환함
=> get 메서드: 특정 쿼리파라미터를 조회, set 메서드: 특정 쿼리파라미터를 업데이트
- 두 번째 원소: 쿼리파라미터를 객체 형태로 업데이트할 수 있는 함수를 반환
[주의할 점]
쿼리파라미터를 조회할 때는 무조건 문자열 타입이라는 점!!!
=> true 또는 false 값을 넣는다면 값을 비교할 때 꼭 'true'와 같이 값을 감싸주고, 숫자를 다룬다면 parseInt를 이용해야 함
- 쿼리스트링 라우팅 예시2 - src/pages/About.js
// About 페이지 컴포넌트: 웹 사이트를 소개
import { useSearchParams } from "react-router-dom";
const About = () => {
const [searchParams, setSearchParams] = useSearchParams(); // useSearchParams이라는 Hook 사용: 배열 타입의 값을 반환하는데, 이 객체는 현재 사용자가 보고 있는 페이지의 정보를 가지고 있음
// useSearchParams의 첫 번째 원소: 쿼리파라미터를 조회하거나 수정하는 메서드들이 담긴 객체를 반환함
const detail = searchParams.get("detail"); // get 메서드: 특정 쿼리파라미터를 조회
const mode = searchParams.get("mode");
const onToggleDetail = () => {
setSearchParams({ mode, detail: detail === "true" ? false : true }); // 쿼리파라미터를 조회할 때는 무조건 문자열 타입이라는 점!!! true 또는 false 값을 넣는다면 값을 비교할 때 꼭 'true'와 같이 값을 감싸주어야 함
};
const onIncreaseMode = () => {
const nextMode = mode === null ? 1 : parseInt(mode) + 1; // 쿼리파라미터를 조회할 때는 무조건 문자열 타입이라는 점!!! 숫자를 다룬다면 parseInt를 이용해야 함
setSearchParams({ mode: nextMode, detail });
};
return (
<div>
<h1>소개</h1>
<p>리액트 라우터를 사용해 보는 프로젝트입니다.</p>
<p>detail: {detail}</p>
<p>mode: {mode}</p>
<button onClick={onToggleDetail}>Toggle detail</button>
<button onClick={onIncreaseMode}>mode +1</button>
</div>
);
};
export default About;
2-5. 중첩된 라우트
리액트 라우터에서 중첩된 리액트를 다루는 방법을 알아봅시다.
우선, 중첩된 라우트는 Route를 두 번이상 중첩되어 쓰는 것입니다.
중첩된 라우트 사용 예
<Route path="/articles" element={<Articles />}>
<Route path=":id" element={<Article />}/>
</Route>
중첩된 라우트 사용 전의 예시와 사용 후의 예시를 들어보겠습니다.
- 중첩된 라우트 사용 전 기존 방식 예시 - src/pages/Articles.js
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;
- 중첩된 라우트 사용 전 기존 방식 예시 - src/pages/Article.js
import { useParams } from "react-router-dom";
const Article = () => {
const { id } = useParams();
return (
<div>
<h2>게시글 {id}</h2>
</div>
);
};
export default Article;
- 중첩된 라우트 사용 전 기존 방식 예시 - src/App.jsArticles와 Article을 추가해줍니다.
import { Route, Routes } from "react-router-dom";
import About from "./pages/About";
import Article from "./pages/Article";
import Articles from "./pages/Articles";
import Home from "./pages/Home";
import Profile from "./pages/Profile";
const App = () => {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/profiles/:username" element={<Profile />} />
<Route path="/articles" element={<Articles />} />
<Route path="/articles/:id" element={<Article />} />
</Routes>
);
};
export default App;
- 중첩된 라우트 사용 전 기존 방식 예시 - src/pages/Home.jsarticles를 연결해줍니다.
// Home 페이지 컴포넌트: 사용자가 웹 사이트에 들어왔을 때 가장 먼저 보여지게 됨
import { Link } from "react-router-dom";
const Home = () => {
return (
<div>
<h1>홈</h1>
<p>가장 먼저 보여지는 페이지입니다.</p>
<ul>
<li>
<Link to="/about">소개</Link>
{/* 리액트 라우터를 사용하는 프로젝트에서는 a태그 대신 Link를 대신 사용, Link역시 a를 사용하긴 하지만 페이지를 새로 불러오는 것을 막고 History API를 통해 브라우저 주소의 경로만 바꾸는 기능이 내장되어 있음 */}
</li>
<li>
<Link to="/profiles/delay100">delay100의 프로필</Link>
</li>
<li>
<Link to="/profiles/gildong">gildong의 프로필</Link>
</li>
<li>
<Link to="/profiles/temp">존재하지 않는 프로필</Link>
</li>
<li>
<Link to="/articles">게시글 목록</Link>
</li>
</ul>
</div>
);
};
export default Home;
이번에는 중첩된 라우트를 사용해봅시다.
게시글 목록 페이지에서 게시글을 열었을 때, 게시글 하단에 목록을 보여줘야 하는 경우 아래와 같이 구현할 수 있습니다.
- 중첩된 라우트 사용 예시1 - src/App.jsArticle을 중첩된 라우트로 변경해주었습니다.
import { Route, Routes } from "react-router-dom";
import About from "./pages/About";
import Article from "./pages/Article";
import Articles from "./pages/Articles";
import Home from "./pages/Home";
import Profile from "./pages/Profile";
const App = () => {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/profiles/:username" element={<Profile />} />
<Route path="/articles" element={<Articles />}>
<Route path=":id" element={<Article />} />
</Route>
</Routes>
);
};
export default App;
- 중첩된 라우트 사용 예시1 - src/pages/Articles.jsOutlet 컴포넌트가 사용된 자리에 중첩된 라우트가 보여지게 됩니다.
// import { Link } from "react-router-dom"; // 중첩된 라우트 사용 전 기존 방식 예시
import { Link, Outlet } from "react-router-dom"; // 중첩된 라우트 사용 예시
const Articles = () => {
return (
<div>
<Outlet />
{/* 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>
</div>
);
};
export default Articles;
게시글 하단에 게시글 목록이 잘 나타나는 것을 확인할 수 있습니다.
중첩된 라우트와 Outlet은 페이지끼리 공통적으로 보여줘야 하는 레이아웃이 있을 때도 유용하게 사용할 수 있습니다.
예를들어, 몇몇 페이지 상단(Home, About, Profile)에 header를 보여줘야 하는 상황이라고 가정합시다.
Header 컴포넌트를 만들어두고, 페이지 컴포넌트에서 재사용하는 방법도 가능하지만 여기서는 중첩된 라우트와 Outlet을 사용해 구현해보겠습니다.
중첩된 라우트 방식을 사용하면 컴포넌트를 한번만 사용해도 된다는 장점이 있습니다.
- 중첩된 라우트 사용 예시2 - src/Layout.js
// 공통 레이아웃을 위한 Layout 컴포넌트
import { Outlet } from "react-router-dom";
const Layout = () => {
return (
<div>
<header style={{ background: "lightgray", padding: 16, fontSize: 24 }}>
Header
</header>
<main>
<Outlet />
{/* 각 페이지 컴포넌트가 보여져야 하는 부분에 Outlet 컴포넌트를 사용*/}
</main>
</div>
);
};
export default Layout;
- 중첩된 라우트 사용 예시2 - src/App.js
import { Route, Routes } from "react-router-dom";
import Layout from "./Layout";
import About from "./pages/About";
import Article from "./pages/Article";
import Articles from "./pages/Articles";
import Home from "./pages/Home";
import Profile from "./pages/Profile";
const App = () => {
return (
<Routes>
<Route element={<Layout />}>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/profiles/:username" element={<Profile />} />
</Route>
<Route path="/articles" element={<Articles />}>
<Route path=":id" element={<Article />} />
</Route>
{/* 중첩된 라우트 사용 전 기존 방식 예시 */}
{/* <Route path="/articles" element={<Articles />} />
<Route path="/articles/:id" element={<Article />} /> */}
</Routes>
);
};
export default App;
App.js에 Home, About, Profile만 중첩시켜놨기에 Articles에 접근했을 때는 header가 사라지는 것을 볼 수 있습니다.
2-6. index props
Route 컴포넌트에는 index라는 props가 존재하는데, 이 props는 path='/'와 동일한 의미입니다.
<Route path="/" element={<Home />} />
// index props 적용 - 좀 더 명시적인 표현 가능
<Route index element={<Home />} />
- index props 적용 예시 - src/App.js
import { Route, Routes } from "react-router-dom";
import Layout from "./Layout";
import About from "./pages/About";
import Article from "./pages/Article";
import Articles from "./pages/Articles";
import Home from "./pages/Home";
import Profile from "./pages/Profile";
const App = () => {
return (
<Routes>
<Route element={<Layout />}>
<Route index element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/profiles/:username" element={<Profile />} />
</Route>
<Route path="/articles" element={<Articles />}>
<Route path=":id" element={<Article />} />
</Route>
{/* 중첩된 라우트 사용 전 기존 방식 예시 */}
{/* <Route path="/articles" element={<Articles />} />
<Route path="/articles/:id" element={<Article />} /> */}
</Routes>
);
};
export default App;
3. 부가 기능
리액트 라우터는 웹 애플리케이션에서 라우팅과 관련된 작업을 할 때 사용할 수 있는 유용한 API를 제공합니다.
그 중 자주 사용되는 것들만 정리해봅시다.
아래의 프로젝트 코드는 2-6까지의 코드가 기본적으로 적용되어 있는 상태입니다.
3-1. useNavigate
useNavigate?
Link 컴포넌트를 사용하지 않고 다른 페이지로 이동해야 하는 상황에 사용하는 Hook
- useNavigate 적용 예시 - src/App.js
// 공통 레이아웃을 위한 Layout 컴포넌트
import { Outlet, useNavigate } from "react-router-dom";
const Layout = () => {
const navigate = useNavigate();
const goBack = () => {
// 이전 페이지로 이동
navigate(-1); // navigate(-1)을 하면 뒤로 한번 감, 파라미터에 -2를 넣으면 뒤로 두 번감, 1을 넣으면 앞으로 한 번가는데, 뒤로가기를 한 번 한 상태여야 함
};
const goArticles = () => {
// articles 경로로 이동
navigate("/articles");
// navigate("/articles", {replace: true}); // replace 옵션: 페이지를 이동할 때 현재 페이지를 페이지 기록에 남기지 않음
};
return (
<div>
<header style={{ background: "lightgray", padding: 16, fontSize: 24 }}>
<button onClick={goBack}>뒤로가기</button>
<button onClick={goArticles}>게시글 목록</button>
</header>
<main>
<Outlet />
{/* 각 페이지 컴포넌트가 보여져야 하는 부분에 Outlet 컴포넌트를 사용*/}
</main>
</div>
);
};
export default Layout;
replace의 옵션이 없는 경우에는
Home -> 소개-> 게시글 에서 뒤로가기를 한 번 누르면 소개페이지로 이동합니다.
replace의 옵션이 true로 있는 경우에는
Home -> 소개-> 게시글 에서 뒤로가기를 한 번 누르면 Home 페이지로 이동합니다.
3-2. NavLink 컴포넌트
NavLink 컴포넌트?
링크에서 사용하는 경로가 현재 라우트의 경로와 일치하는 경우 특정 스타일 또는 CSS 클래스를 적용하는 컴포넌트
- 이 컴포넌트의 style과 className은 { isActivate: boolean }을 파라미터로 전달받는 함수 타입의 값을 전달함
- NavLink 컴포넌트 예시- src/pages/Articles.js
// import { Link } from "react-router-dom"; // 중첩된 라우트 사용 전 기존 방식 예시
// import { Link, Outlet } from "react-router-dom"; // 중첩된 라우트 사용 예시
import { NavLink, Outlet } from "react-router-dom"; // NavLink 컴포넌트
const Articles = () => {
const activeStyle = {
color: "green",
fontSize: 21,
};
return (
<div>
<Outlet />
{/* Outlet 컴포넌트가 사용된 자리에 중첩된 라우트가 보여지게 됩니다. */}
<ul>
<li>
{/*
<Link to="/articles/1">게시글 1</Link>
*/}
<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>
</div>
);
};
export default Articles;
- NavLink 컴포넌트 예시 (또 다른 컴포넌트를 만들어 리팩토링 함)- src/pages/Articles.js
// 아래부터는 리팩토링 된 Articles.js
import { NavLink, Outlet } from "react-router-dom"; // NavLink 컴포넌트
const Articles = () => {
return (
<div>
<Outlet />
{/* 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/${id}`}
style={({ isActive }) => (isActive ? activeStyle : undefined)}
>
게시글 {id}
</NavLink>
</li>
);
};
export default Articles;
현재 접속해있는 게시글에는 표시가 다르게 되는 것을 볼 수 있습니다.
3-3. NotFound 페이지
NotFound 페이지를 만드는 방법을 소개합니다. 이 페이지는 사전에 정의되지 않는 경로에 사용자가 진입했을 때 보여주는 페이지입니다.
즉, 페이지를 찾을 수 없을 때 나타나는 페이지입니다.
- NotFound 페이지 만들기 예시 - src/pages/NotFound.js
const NotFound = () => {
return (
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
fontSize: 64,
position: "absolute",
width: "100%",
height: "100%",
}}
>
404
</div>
);
};
export default NotFound;
- NotFound 페이지 만들기 예시 - src/App.js
import Notfound from "./pages/NotFound";
//...생략
const App = () => {
return (
<Routes>
{/* ...생략 */}
<Route path="*" element={<NotFound />} />
{/* *는 wildcard 문자: 아무 텍스트나 매칭한다는 뜻, 이 라우트 엘리먼트의 상단에 위치하는 라우트들의 규칙을 모두 확인하고, 일치하는 라우트가 없다면 이 라우트가 화면에 나타나게 됨*/}
</Routes>
);
};
export default App;
3-4. Navigate 컴포넌트
Navigate 컴포넌트?
컴포넌트를 화면에 보여주는 순간 다른 페이지로 이동을 하고 싶을 때 사용하는 컴포넌트
아래의 예시는 사용자의 로그인이 필요한 페이지인데(MyPage) 로그인을 안 한경우 로그인 페이지로 이동합니다.
- Navigate 컴포넌트 예시 - src/pages/Login.js
const Login = () => {
return <div>로그인 페이지</div>;
};
export default Login;
- Navigate 컴포넌트 예시 - src/pages/MyPage.js
import { Navigate } from "react-router-dom";
const MyPage = () => {
const isLoggedIn = false; // 사용자가 로그인을 하지 않은 경우
if (!isLoggedIn) {
return <Navigate to="/login" replace={true} />; // login 페이지로 이동, replace 옵션: 페이지를 이동할 때 현재 페이지를 페이지 기록에 남기지 않음(뒤로가기 클릭 시 두 페이지 전의 페이지로 이동)
}
return <div>마이 페이지</div>;
};
export default MyPage;
- Navigate 컴포넌트 예시 - src/App.js
import Login from "./pages/Login";
import MyPage from "./pages/MyPage";
const App = () => {
return (
<Routes>
{/* ...생략 */}
<Route path="/login" element={<Login />} />
<Route path="/mypage" element={<MyPage />} />
</Routes>
);
};
export default App;
실행 결과를 보면, mypage로 이동하려 하는데 login이 되어있지 않아 login 페이지로 이동됩니다.
또한 뒤로가기 버튼을 한 번 누르면 mypage가 아닌 홈 화면으로 이동되는 것을 볼 수 있습니다.
읽어주셔서 감사합니다. 잘못된 정보는 댓글로 알려주세요!
'Study > React' 카테고리의 다른 글
15. Context API (0) | 2022.07.30 |
---|---|
14. 뉴스 뷰어 만들기(with. newsapi) (0) | 2022.07.27 |
12. 불변성 유지하기(immer) (0) | 2022.07.21 |
11. 컴포넌트 성능 최적화 (0) | 2022.07.20 |
10. 간단한 투두리스트(TodoList) 만들기 (0) | 2022.07.19 |