티스토리 뷰
React Router v6 튜토리얼
이 내용은 해당 블로그의 내용을 정리 및 요약하였습니다.
1. 라우팅이란?
사용자가 요청한 URL에 따라 알맞는 페이지를 보여주는 것.
리액트에서 라우트 시스템을 구축하기 위해 사용할 수 있는 방법 2가지
- 리액트 라우터 (React Router)
- Next.js - Next.js는 리액트 프로젝트의 프레임워크. 이 프레임워크는 우리가 사용했던 Create React App처럼 리액트 프로젝트 설정을 하는 기능, 라우팅 시스템, 최적화, 다국어 시스템 지원, 서버 사이드 렌더링 등 다양한 기능들을 제공한다. 이 프레임워크 라우팅 시스템은 파일 경로 기반으로 작동한다. 이 프레임워크는 리액트 라우터의 대안으로 많이 사용되고 있다.
리액트 라우터를 사용하면 손쉽게 리액트 라우터로 싱글 페이지 애플리케이션 (Single Page Application)을 만들 수 있다.
💡 싱글 페이지 애플리케이션(SPA)란?
기존의 멀티 페이지 애플리케이션에서는 사용자가 다른 페이지로 이동할 때마다 새로운 html을 받아오고, 페이지를 로딩할 때마다 서버에서 CSS, JS, 이미지 파일 등의 리소스를 전달받아 브라우저 화면에 보여준다. 각 페이지마다 다른 html 파일을 만들어서 제공을 하거나, 데이터에 따라 유동적인 html을 생성해 주는 템플릿 엔진을 사용하기도 한다.
사용자 인터렉션이 별로 없는 정적인 페이지들은 기존의 방식이 적합하지만, 사용자 인터랙션이 많도 다양한 정보를 제공하는 모던 웹 애플리케이션은 이 방식이 적합하지 않다. 새로운 페이지를 보여주어야 할 때마다 서버 측에서 모든 준비를 한다면 그만큼 서버의 자원을 사용하는 것이고, 트래픽도 더 많이 나올 수 있기 때문이다.
그래서, 리액트 같은 라이브러리를 사용해서 뷰 렌더링을 사용자의 브라우저가 담당하도록 하고, 우선 웹 애플리케이션을 브라우저에 불러와서 실행시킨 후에 사용자와의 인터랙션이 발생하면 필요한 부분만 자바스크립트를 사용하여 업데이트하는 방식을 사용하게 되었다. 만약 새로운 데이터가 필요하다면 서버 API를 호출하여 필요한 데이터만 새로 불러와 애플리케이션에서 사용할 수 있다.
이렇게 html은 한번만 받아와서 웹 애플리케이션을 실행시킨 후, 그 이후에는 필요한 데이터만 받아와서 화면에 업데이트 해주는 것이 싱글 페이지 애플리케이션이다.
SPA는 기술적으로 한 페이지만 존재하는 것이지만, 사용자가 경험하기에는 여러페이지가 존재하는 것 처럼 느낄 수 있다. 리액트 라우터와 같은 라우팅 시스템은 사용자의 브라우저 주소창의 경로에 따라 알맞은 페이지를 보여주는데, 이후 링크를 눌러서 다른 페이지로 이동하게 될 대 서버에 다른 페이지의 html을 새로 요청하는 것이 아니라, 브라우저의 History API를 사용하여 브라우저의 주소창의 값만 변경하고 기존에 페이지에 띄었던 웹 애플리케이션을 그대로 유지하면서 라우팅 설정에 따라 또 다른 페이지를 보여주게 된다.
2. 리액트 라우터 설정
1) 라이브러리 설치
리액트 라우터를 사용하려면 react-router-dom이라는 라이브러리를 설치해야 한다.
npm install react-router-dom
2) 프로젝트에 적용
프로젝트에 리액트 라우터를 적용할 때는 src/index.js 파일에서 react-router-dom에 내장되어 있는 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();
3) 페이지 컴포넌트 만들기
src/pages/Homs.js
const Home = () => {
return (
<div>
<h1>홈</h1>
<p>가장 먼저 보여지는 페이지입니다.</p>
</div>
);
};
export default Home;
src/pages/About.js
const About = () => {
return (
<div>
<h1>소개</h1>
<p>리액트 라우터를 사용해 보는 프로젝트입니다.</p>
</div>
);
};
export default About;
4) Route 컴포넌트로 특정 경로에 원하는 컴포넌트 보여주기
사용자의 브라우저 주소 경로에 따라 우리가 원하는 컴포넌트를 보여주기 위해서 Route라는 컴포넌트를 통해 라우트 설정을 해줘야 한다.
Route 컴포넌트는 다음과 같이 사용한다.
<Route path="주소규칙" element={보여 줄 컴포넌트 JSX} />
그리고, Route 컴포넌트는 Routes 컴포넌트 내부에서 사용되어야 한다.
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>
);
};
5) Link 컴포넌트를 사용하여 다른 페이지로 이동하는 링크 보여주기
원래 웹 페이지에서는 링크를 보여줄 때 a태그를 사용하는데, 리액트 라우터를 사용하는 프로젝트에서는 a태그를 사용하지 않는다. a태그를 클릭하여 페이지를 이동할 때 브라우저에서는 페이지를 새로 불러오기 때문이다.
Link 컴포넌트 역시 a태그를 사용하긴 하지만, 페이지를 새로 불러오는 것을 막고 History API를 통해 브라우저 주소의 경로만 바꾸는 기능이 내장되어 있다.
Link 컴포넌트는 다음과 같이 사용한다.
<Link to="경로">링크 이름</Link>
Home 페이지에서 About 페이지로 이동할 수 있도록 Link 컴포넌트를 Home 페이지 컴포넌트에서 사용해보자!
src/pages/Home.js
import { Link } from 'react-router-dom';
const Home = () => {
return (
<div>
<h1>홈</h1>
<p>가장 먼저 보여지는 페이지입니다.</p>
<Link to="/about">소개</Link>
</div>
);
};
export default Home;
3. URL 파라미터와 쿼리스트링
1) URL 파라미터
src/pages/Profile.js
import { useParams } from 'react-router-dom';
const data = {
velopert: {
name: '김민준',
description: '리액트를 좋아하는 개발자',
},
gildong: {
name: '홍길동',
description: '고전 소설 홍길동전의 주인공',
},
};
const Profile = () => {
const params = useParams();
const profile = data[params.username];
return (
<div>
<h1>사용자 프로필</h1>
{profile ? (
<div>
<h2>{profile.name}</h2>
<p>{profile.description}</p>
</div>
) : (
<p>존재하지 않는 프로필입니다.</p>
)}
</div>
);
};
export default Profile;
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 파라미터는 useParams라는 Hook을 사용하여 객체 형태로 조회할 수 있다. URL 파라미터 이름은 라우트 설정할 때 Route 컴포넌트의 path props를 통해 설정한다. URL 파라미터는 /profiles/:username 과 같이 경로에 : 를 사용하여 설정한다. 만약 URL 파라미터가 여러개인 경우엔 /profiles/:username/:field 와 같은 형태로 설정할 수 있다.
2) 쿼리스트링
쿼리스트링을 사용할 때는 URL 파라미터와 달리 Route 컴포넌트를 사용할 때 별도로 설정해야되는 것은 없다.
src/pages/About.js
import { useLocation } from 'react-router-dom';
const About = () => {
const location = useLocation();
return (
<div>
<h1>소개</h1>
<p>리액트 라우터를 사용해 보는 프로젝트입니다.</p>
<p>쿼리스트링: {location.search}</p>
</div>
);
};
export default About;
위 컴포넌트에서 useLocation 이라는 Hook을 사용했는데, 이 Hook은 location 객체를 반환하며 이 객체는 현재 사용자가 보고있는 페이지의 정보를 지니고 있다. 이 객체에는 다음과 같은 값들이 있다.
- pathname: 현재 주소의 경로 (쿼리스트링 제외)
- search: 맨 앞의 ? 문자 포함한 쿼리스트링 값
- hash: 주소의 # 문자열 뒤의 값 (주로 History API 가 지원되지 않는 구형 브라우저에서 클라이언트 라우팅을 사용할 때 쓰는 해시 라우터에서 사용합니다.)
- state: 페이지로 이동할때 임의로 넣을 수 있는 상태 값
- key: location 객체의 고유 값, 초기에는 default 이며 페이지가 변경될때마다 고유의 값이 생성됨
쿼리스트링을 따로 파싱까지 해야된다면 번거로울 수 있는데, 리액트 라우터 v6부터 useSearchParams 라는 Hook을 통해 쿼리스트링을 더욱 쉽게 다룰 수 있다.
useSearchParams 는 배열 타입의 값을 반환하며, 첫번째 원소는 쿼리파라미터를 조회하거나 수정하는 메서드들이 담긴 객체를 반환한다. get 메서드를 통해 특정 쿼리파라미터를 조회할 수 있고, set 메서드를 통해 특정 쿼리파라미터를 업데이트 할 수 있다. 만약 조회시에 쿼리파라미터가 존재하지 않는다면 null 로 조회된다. 두번째 원소는 쿼리파라미터를 객체형태로 업데이트할 수 있는 함수를 반환한다.
쿼리파라미터를 사용하실 때 주의할 점은 쿼리파라미터를 조회할 때 값은 무조건 문자열 타입이라는 것이다. 즉, true 또는 false 값을 넣게 된다면 값을 비교할 때 꼭 'true' 와 같이 따옴표로 감싸서 비교를 해야 하고, 숫자를 다루게 된다면 parseInt 를 사용하여 숫자 타입으로 변환을 해야 한다.
src/pages/About.js
import { useSearchParams } from 'react-router-dom';
const About = () => {
const [searchParams, setSearchParams] = useSearchParams();
const detail = searchParams.get('detail');
const mode = searchParams.get('mode');
const onToggleDetail = () => {
setSearchParams({ mode, detail: detail === 'true' ? false : true });
};
const onIncreaseMode = () => {
const nextMode = mode === null ? 1 : parseInt(mode) + 1;
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;
5. 중첩된 라우트
Outlet 컴포넌트를 이용하여 중첩된 라우트 설정을 할 수 있다. 다음은 그 사용 예시이다. Outlet 컴포넌트는 Route 의 children 으로 들어가는 JSX 엘리먼트를 보여주는 역할을 한다.
src/pages/Articles.js
import { Link, Outlet } from 'react-router-dom';
const Articles = () => {
return (
<div>
<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;
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.js
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) 공통 레이아웃 컴포넌트
중첩된 라우트와 Outlet 컴포넌트는 페이지끼리 공통적으로 보여줘야 하는 레이아웃이 있을때도 유용하게 사용할 수 있다. 예를 들어서, Home, About, Profile 페이지에서 상단에 헤더를 보여줘야 하는 경우 방금 배운 중첩된 라우트와 Outlet을 활용하여 구현을 할 수도 있다.
src/Layout.js
import { Outlet } from 'react-router-dom';
const Layout = () => {
return (
<div>
<header style={{ background: 'lightgray', padding: 16, fontSize: 24 }}>
Header
</header>
<main>
<Outlet />
</main>
</div>
);
};
export default Layout;
각 페이지 컴포넌트가 보여져야 하는 부분에 Outlet 컴포넌트를 사용해주었다. 컴포넌트를 다 작성했으면 App 컴포넌트를 다음과 같이 수정 한다.
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>
</Routes>
);
};
export default App;
2) index props
Route 컴포넌트에는 index 라는 props가 있다. 이 props 는 path="/"와 동일한 의미를 가진다.
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 path="/" 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>
</Routes>
);
};
export default App;
index prop은 상위 라우트의 경로와 일치하지만, 그 이후에 경로가 주어지지 않았을때 보여지는 라우트를 설정할때 사용한다. path="/"와 동일한 역할을 하며 이를 좀 더 명시적으로 표현하는 방법이다.
6. 리액트 라우터 부가기능
리액트 라우터에는 웹 애플리케이션에서 라우팅에 관련된 작업을 할 때 사용할 수 있는 유용한 API들을 제공한다.
1) useNavigate
useNavigate 는 Link 컴포넌트를 사용하지 않고 다른 페이지로 이동을 해야 하는 상황에 사용하는 Hook이다.
src/Layout.js
import { Outlet, useNavigate } from 'react-router-dom';
const Layout = () => {
const navigate = useNavigate();
const goBack = () => {
// 이전 페이지로 이동
navigate(-1);
};
const goArticles = () => {
// articles 경로로 이동
navigate('/articles');
};
return (
<div>
<header style={{ background: 'lightgray', padding: 16, fontSize: 24 }}>
<button onClick={goBack}>뒤로가기</button>
<button onClick={goArticles}>게시글 목록</button>
</header>
<main>
<Outlet />
</main>
</div>
);
};
export default Layout;
navigate 함수를 사용할 때 파라미터가 숫자 타입이라면 앞으로 가거나, 뒤로 간다. 예를 들어서 navigate(-1) 을 하면 한 번 뒤로가고 navigate(-2) 를 하면 두 번 뒤로간다. 반대로 navigate(1) 을 하면 앞으로 한 번 간다. 물론, 뒤로가기를 한번 한 상태여야 한다.
다른 페이지로 이동을 할 때 replace 라는 옵션이 있는데, 이 옵션을 사용하면 페이지를 이동할 때 현재 페이지를 페이지 기록에 남기지 않는다.
src/Layout.js - goArticles
const goArticles = () => {
navigate('/articles', { replace: true });
}
2) NavLink
NavLink 컴포넌트는 링크에서 사용하는 경로가 현재 라우트의 경로와 일치하는 경우 특정 스타일 또는 CSS 클래스를 적용하는 컴포넌트다. 이 컴포넌트를 사용할 때 style 또는 className을 설정할 때 { isActive: boolean } 을 파라미터로 전달받는 함수 타입의 값을 전달한다.
<NavLink
style={({isActive}) => isActive ? activeStyle : undefined}
/>
<NavLink
className={({isActive}) => isActive ? 'active' : undefined}
/>
3) 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;
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 NotFound from './pages/NotFound';
import Profile from './pages/Profile';
const App = () => {
return (
<Routes>
<Route path="/" 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="*" element={<NotFound />} />
</Routes>
);
};
export default App;
여기서 * 는 wildcard 문자인데, 이는 아무 텍스트나 매칭한다는 뜻이다. 이 라우트 엘리먼트의 상단에 위치하는 라우트들의 규칙을 모두 확인하고, 일치하는 라우트가 없다면 이 라우트가 화면에 나타나게 된다.
4. Navigate 컴포넌트
Navigate 컴포넌트는 컴포넌트를 화면에 보여주는 순간 다른 페이지로 이동을 하고 싶을 때 사용하는 컴포넌트다. 즉, 페이지를 리다이렉트 하고 싶을 때 사용한다.
src/pages/MyPage.js
import { Navigate } from 'react-router-dom';
const MyPage = () => {
const isLoggedIn = false;
if (!isLoggedIn) {
return <Navigate to="/login" replace={true} />;
}
return <div>마이 페이지</div>;
};
export default MyPage;
여기서 isLoggedIn은 현재 false라는 고정값을 가지고 있지만, 이 값이 로그인 상태에 따라 true 또는 false를 가르킨다고 가정을 해보자.
위 컴포넌트에서는 만약 이 값이 false 라면 Navigate 컴포넌트를 통해 /login 경로로 이동한다. 여기서 replace props는 useNavigate 에서 설명한 것과 동일하다. 페이지를 이동할 때 현재 페이지를 기록에 남기지 않기 때문에 이동 후 뒤로가기를 눌렀을 때 2 페이지 전의 페이지로 이동한다.
'개발 > React' 카테고리의 다른 글
React 상태관리 라이브러리 - Redux vs Recoil (1) | 2023.05.11 |
---|---|
React 기본 - Component, Props, Event, State (0) | 2023.05.03 |
React 기본 - UseEffect (0) | 2023.03.09 |
React App 만들기(2) - Button 컴포넌트 만들기 (0) | 2023.03.08 |
React App 만들기(1) - 프로젝트 생성 (1) | 2023.03.08 |
- Total
- Today
- Yesterday
- 리액트
- 간식
- 타코
- frontend
- 다국어
- recoil
- .env
- Redux
- 하루견과
- Componenet
- 산과들에
- 타입스크립트
- 유지어터
- 환경변수
- Next.js
- 다이어트
- 견과류
- 닭가슴살
- react-i18next
- 프론트엔드
- 읍천리382
- React
- TypeScript
- 프로트엔드
- 스벨트
- svelte
- props
- 을지로
- useEffect
- 프로틴
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 |