1. 리액트 라우터 v6 소개
React Router란?
React Router
는 React
컴포넌트로 페이지를 나누는 라우팅 라이브러리입니다.
React
와 함께 SPA(Single-Page Application)
를 구축하는 데 활용됩니다.
React Router의 역사
- 2014년 Facebook에서 최초로 공개했습니다.
React
애플리케이션에서 라우팅을 선언적으로 처리할 수 있게 되었고, 애플리케이션의 상태와 URL 경로를 쉽게 동기화할 수 있게 되었습니다.
- 2017년 React Router v4에서는 전체적인 API가 개선 되었고, 선언적인 라우팅을 위해 JSX를 사용하는 방식으로 변경되었습니다.
- 2019년 React Router v5에서는 React Hooks를 지원하게 되었습니다.
- 2021년 React Router v6에서는 v5로부터의 변경점 참조
React Router의 특징
Client Side Routing
- React Router는
client-side routing
을 지원합니다.
- 예전에는 웹사이트에서 브라우저는 페이지를 이동하면, 서버로부터
GET request
를 보내 해당 URL 경로에 따른 적절한 페이지나 데이터를 리턴받는 server-side routing
을 사용했습니다.
Client side routing
은 앱이 서버로 request 보내지 않아도 링크 클릭만으로 URL을 업데이트할 수 있게 해줍니다. 즉시 새로운 UI를 렌더하고 새로운 정보로 페이지를 업데이트하기 위해 fetch
데이터 리퀘스트를 생성합니다.
Client side routing
은 브라우저가 전체 문서를 요청하거나 CSS, JavaScript를 재차 적용할 필요가 없기 때문에 더 빠른 UX를 제공합니다. Client side routing
은 애니메이션 같은 dynamic UX도 제공할 수 있습니다.
Client side routing
은 Router
를 만들고 Link
나 <Form>
으로 페이지끼리 연결하여 사용할 수 있습니다.
Nested Routes
React Router
v4 이후부터 도입된 개념으로, 하나의 라우터 안에 다른 라우터를 중첩해서 사용하는 것을 의미합니다.
- 하나의 페이지 내에서 여러 개의 라우트를 관리할 수 있게 해줍니다.
- 시각화
Dynamic Segments
URL segments
는 dynamic(동적인) placeholder
가 될 수 있습니다. 이는 파싱되어 api에 제공됩니다.
Ranked Route Matching
- URL들과 route들을 매칭할 때, React Router는 segments, static segments, dynamic segments, splats 등의 갯수에 따라 route에 우선 순위를 정합니다. 우선 순위가 높은 route에 URL을 매칭합니다.
Active Links
<NavLink>
를 통해 active navigation items를 쉽게 스타일링하여 유저가 앱 내 어디에 있는지(isActive) 또는 어디로 가고 있는지(isPending)를 알 수 있게 해줍니다.
- 링크 외부의
active
표시도 useMatch
hook으로 할 수 있습니다.
Relative Links
- HTML의
<a href>
처럼 <Link to>
와 <NavLink to>
도 relative path로 지정할 수 있습니다.
..
은 route path를 의미합니다.
..
을 relative path로 사용하려면 relative="path"
를 추가해야 합니다.
Data Loading
- URL에 매핑되는 애플리케이션 데이터를 렌더링하기 전에 로드할 수 있습니다.
- 중첩 라우트와 결합하여, 멀티 레이아웃의 모든 데이터들을 병렬적으로 로드할 수 있습니다.
Redirects
- 데이터를 바꾸거나 로드하면, 일반적으로 유저를 다른 route로 redirect합니다.
Pending Navigation UI
- 유저가 앱을 탐색할 때 다음 페이지가 렌더되기 전에 로드됩니다. 앱이 응답하지 않는다고 느끼지 않도록 이 시간 동안 유저 피드백을 제공하는 것이 중요합니다.
Skeleton UI with <Suspense>
- 다음 페이지의 데이터가 로드되는 동안 UI가 placeholder UI와 함께 즉시 다음 화면으로 넘어가도록 데이터를 지연할 수 있습니다.
Data Mutations
Client side routing
방식으로 HTML form workflows를 지원합니다.
Data Revalidation
- 라우트에 연결된 데이터를 주기적으로 재검사하고 업데이트하는 기능입니다. 이를 통해 애플리케이션의 데이터가 항상 최신 상태로 유지됩니다.
Busy Indicators
- Form이 서브밋되는 중일 때,
busy indicators
, disable fieldsets
등으로 상태 안내를 할 수 있습니다.
Optimistic UI
- 작업으로 전송되는 데이터 형식을 알면 비동기 작업이 보류 중인 경우에도
busy indicators
를 스킵하고 UI를 다음 상태로 즉시 렌더링할 수 있습니다.
Data Fetchers
Data Fetchers
는 브라우저에서 navigation
을 유발하지 않고 route actions과 loaders와 상호작용할 수 있게 해줍니다. 이는 여전히 error handling
, revalidation
, interruption handling
, race condition handling
같은 이점들을 유지합니다.
Race Condition Handling
useEffect
hook에서 리턴한 함수를 사용하여 자동으로 오래된 작업을 취소하고 새 데이터만 로드할 수 있습니다.
Error Handling
Scroll Restoration
- 페이지 전환이나 URL 변경 후에 이전 스크롤 위치를 유지하는 기능입니다.
Web Standard APIs
- 웹 표준 API를 기반으로 합니다.
- Loaders와 actions는 표준 Web Fetch API Request objects를 받고 response objects를 리턴할 수 있습니다.
Cancellation
은 Abort Signals
로 수행되고, Search Params
는 URLSearchParams
로 처리되며, data mutations
는 HTML Forms
로 처리됩니다.
Search Params
URLSearchParams
API를 사용하여 구현됩니다. 이를 통해 URL의 쿼리 문자열을 쉽게 파싱하고, Search Params
객체를 사용하여 필요한 쿼리 매개변수를 추출하거나 새로운 쿼리 문자열을 생성할 수 있습니다.
Location State
history.push()
또는 history.replace()
메서드들의 두 번째 파라미터로 전달할 수 있는 객체입니다.
- State data를 다음 컴포넌트나 페이지의 경로와 함께 전달하는 기능을 지원합니다.
- React Router 공식 문서 참조
React Router의 구성 요소
Router
- React Router에서 기본적인 라우팅 기능을 제공하는 최상위 컴포넌트입니다.
- Router 컴포넌트도 내부적으로 context provider이므로 프로젝트 전체에 react router 기능을 사용하려면
Router
안에 컴포넌트들을 넣어야합니다.
BrowserRouter
와 HashRouter
등의 서브클래스가 있으며, 사용하고자 하는 브라우저 히스토리 API에 따라 선택하여 사용할 수 있습니다.
Routes
Route
컴포넌트들을 정의하고 렌더링하는 컴포넌트입니다.
- 이 컴포넌트는 여러 개의 Route 컴포넌트를 포함할 수 있으며, Route 컴포넌트에 대한 매칭 규칙을 지정할 수 있습니다.
Route
- URL 경로와 매칭되는 컴포넌트를 정의하는 컴포넌트입니다.
path
프로퍼티를 사용하여 URL 경로를 정의하고, component 프로퍼티를 사용하여 매칭될 때 렌더링될 컴포넌트를 지정할 수 있습니다. 또한, exact 프로퍼티를 사용하여 정확한 매칭을 지정할 수 있습니다.
Link
- 다른 경로로 이동하는 링크를 생성하는 컴포넌트입니다.
- 이를 통해 브라우저 히스토리를 업데이트하고, 브라우저를 다른 경로로 이동시킬 수 있습니다.
- to 프로퍼티를 사용하여 이동할 경로를 지정할 수 있으며, href 속성이 자동으로 추가됩니다.
2. Routes로 페이지 나누기
import { BrowserRouter, Routes, Route } from "react-router-dom";
import App from "./components/App";
import HomePage from "./pages/HomePage";
import CoursePage from "./pages/CoursePage";
import CourseListPage from "./pages/CourseListPage";
import WishlistPage from "./pages/WishlistPage";
function Main() {
return (
<BrowserRouter>
<App>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="courses" element={<CourseListPage />} />
<Route
path="courses/react-frontend-development"
element={<CoursePage />}
/>
<Route path="wishlist" element={<WishlistPage />} />
</Routes>
</App>
</BrowserRouter>
);
}
export default Main;
- Routes 컴포넌트로 Route 컴포넌트들을 감싸고, Route 컴포넌트의 path prop으로 경로를, element prop으로 보여줄 컴포넌트를 jsx형식으로 내려줍니다.
- Routes를 렌더링할 때 React Router는 Routes 안에 있는 Route를 차례대로 검사하면서 현재 경로가 path와 일치하는지 하나씩 검사하고, 일치하는 경로를 찾으면 지정한 컴포넌트로 렌더링해줍니다.
3. Link로 이동하기
import logoImg from "../assets/logo.svg";
import { Link } from "react-router-dom";
function Nav() {
return (
...
<Link to="/">
<img src={logoImg} alt="Codethat Logo" />
</Link>
...
<li>
<Link to="/courses">카탈로그</Link>
</li>
...
);
}
<a href>
태그가 있을 자리에 대신 <Link>
컴포넌트를 사용합니다.
to
prop으로 이동할 경로를 문자열이나 템플릿 문자열으로 내려줍니다.
4. NavLink로 네비게이션 구현하기
import { Link, NavLink } from "react-router-dom";
function getLinkStyle({ isActive }) {
return {
textDecoration: isActive ? "underline" : undefined,
};
}
function Nav() {
return (
...
<NavLink to="/courses" style={getLinkStyle}>
카탈로그
</NavLink>
...
<NavLink to="/questions" style={getLinkStyle}>
커뮤니티
</NavLink>
);
}
<NavLink>
는 <Link>
와 비슷하게 작동하지만, 현재 경로와 매칭되는 경우에 스타일을 적용하는 기능을 추가로 제공하는 컴포넌트입니다.
<Link>
와 다른 점은 style prop으로 함수를 지정해 줄 수 있다는 점입니다.
isActive
값은 <NavLink>
컴포넌트가 현재 경로와 매치되는지 여부에 따라 자동으로 설정됩니다.
5. 하위 페이지 나누기
import { BrowserRouter, Routes, Route } from "react-router-dom";
import App from "./components/App";
import HomePage from "./pages/HomePage";
import CoursePage from "./pages/CoursePage";
import CourseListPage from "./pages/CourseListPage";
import WishlistPage from "./pages/WishlistPage";
import QuestionListPage from "./pages/QuestionListPage";
import QuestionPage from "./pages/QuestionPage";
function Main() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<App />}>
<Route index element={<HomePage />} />
<Route path="courses">
<Route index element={<CourseListPage />} />
<Route path="react-frontend-development" element={<CoursePage />} />
</Route>
<Route path="wishlist" element={<WishlistPage />} />
<Route path="questions" element={<QuestionListPage />} />
<Route path="questions/616825" element={<QuestionPage />} />
</Route>
</Routes>
</BrowserRouter>
);
}
export default Main;
import { Outlet } from "react-router-dom";
import Nav from "../components/Nav";
import Footer from "../components/Footer";
import styles from "./App.module.css";
import "./App.font.css";
function App() {
return (
<>
<Nav className={styles.nav} />
<div className={styles.body}>
<Outlet />
</div>
<Footer className={styles.footer} />
</>
);
}
export default App;
Nested routes
를 사용하여 하위 페이지를 나눌 수 있습니다.
- Index에 해당하는 route에는 path 대신 index prop을 내려줍니다.
- 하위 Route에서 공통된 디자인을 보여주고 싶다면 해당 컴포넌트를 Route 컴포넌트의 element prop으로 지정하고, 지정된 컴포넌트에서
<Outlet>
컴포넌트를 사용하면 됩니다.
- Routes 컴포넌트 내에 App 컴포넌트를 넣을 수 없기 때문이다.
<Outlet>이란?
Nested Routing
을 위해 사용되는 라우트 컴포넌트(Route component)에서 중첩된 자식 라우트를 렌더링할 때 사용됩니다.
<Outlet>
은 이 자식 라우트를 렌더링할 위치를 나타내는 플레이스홀더(placeholder) 역할을 합니다.
<Outlet>
은 중첩된 라우팅에서 라우트 컴포넌트에서 자식 라우트를 렌더링할 때 반드시 사용해야 하는 것은 아니지만, <Outlet>
을 사용하면 중첩된 라우팅을 더욱 직관적이고 가독성 좋게 작성할 수 있습니다.
6. useParams로 동적인 경로 만들기
동적 경로(Dynamic Segments)
- React Router에서 URL 경로에 동적으로 변하는 값을 포함시키는 방법입니다.
- URL 경로에 콜론(:)과 함께 동적으로 변하는 값의 이름(parameter)을 지정합니다. 이렇게 정의된 동적 경로는
useParams()
hook을 사용하여 라우트 컴포넌트에서 접근할 수 있습니다.
import { BrowserRouter, Routes, Route } from "react-router-dom";
import CoursePage from "./pages/CoursePage";
import QuestionPage from "./pages/QuestionPage";
...
function Main() {
return (
<BrowserRouter>
<Routes>
...
<Route path="courses">
...
<Route path=":courseSlug" element={<CoursePage />} />
</Route>
...
<Route path="questions">
...
<Route path=":questionId" element={<QuestionPage />} />
</Route>
...
</Routes>
</BrowserRouter>
);
}
import { useParams } from "react-router-dom";
import { getCourseBySlug } from "../api";
...
function CoursePage() {
const { courseSlug } = useParams();
const course = getCourseBySlug(courseSlug);
...
}
import { useParams } from "react-router-dom";
import { getQuestionById } from "../api";
...
function QuestionPage() {
const { questionId } = useParams();
const question = getQuestionById(questionId);
...
}
7. 없는 페이지 처리하기
- 일치하는 path가 없을 경우 빈 화면을 보여줍니다.
<Route path="*" elment={<NotFoundPage />} />
Wildcard character ("*")
로 경로를 지정한 라우트 컴포넌트를 맨 마지막에 추가하면 지정된 경로가 아닌 모든 경로의 페이지를 한 번에 디자인할 수 있습니다.
8. Navigate로 리다이렉트 하기
Redirect
- 리다이렉트(Redirect)란, 웹 사이트에서 페이지나 URL을 다른 페이지나 URL로 변경하는 기능을 말합니다.
<Navigate>
- React Router v6에서 도입된 컴포넌트 중 하나로, to prop을 사용하여 다른 경로로 사용자를 리디렉션할 수 있습니다.
import { Navigate } from "react-router-dom";
...
function CoursePage() {
...
if (!course) {
return <Navigate to="/courses" />;
}
...
}
9. useSearchParams로 쿼리 사용하기
useSearchParams란?
useSearchParams()
는 React Router v6에서 도입된 Hook 중 하나로, URL의 쿼리 파라미터를 사용하여 상태(state)를 관리할 수 있도록 해줍니다.
- URL의 쿼리 파라미터를 읽거나 변경하여 다른 컴포넌트에서 사용할 수 있는 객체를 리턴할 수 있게 해줍니다.
쿼리 사용하기
function CourseListPage() {
const [searchParam, setSearchParam] = useSearchParams();
const initKeyword = searchParam.get("keyword");
const [keyword, setKeyword] = useState(initKeyword || "");
const courses = getCourses(initKeyword);
const handleKeywordChange = (e) => setKeyword(e.target.value);
const handleSubmit = (e) => {
e.preventDefault();
setSearchParam(keyword ? { keyword } : {});
};
return (
<ListPage
variant="catalog"
title="모든 코스"
description="자체 제작된 코스들로 기초를 쌓으세요."
>
<form className={searchBarStyles.form} onSubmit={handleSubmit}>
<input
name="keyword"
value={keyword}
onChange={handleKeywordChange}
placeholder="검색으로 코스 찾기"
></input>
<button type="submit">
<img src={searchIcon} alt="검색" />
</button>
</form>
<p className={styles.count}>총 {courses.length}개 코스</p>
{initKeyword && courses.length === 0 ? (
<Warn
className={styles.emptyList}
title="조건에 맞는 코스가 없어요."
description="올바른 검색어가 맞는지 다시 한 번 확인해 주세요."
/>
) : (
<div className={styles.courseList}>
{courses.map((course) => (
<CourseItem key={course.id} course={course} />
))}
</div>
)}
</ListPage>
);
}
export default CourseListPage;
useSearchParam()
으로 생성된 searchParam
은 객체이므로 searchParam.get(key)
메서드로 값에 접근해야 합니다.
10. useNavigate로 페이지 이동하기
useNavigate란?
useNavigate()
는 자바스크립트 코드로 브라우저의 주소를 변경할 수 있게 해주는 커스텀 훅입니다.
- 특정한 코드의 실행이 끝나고 나서 페이지를 이동시키고 싶을 때 사용합니다.
페이지 이동하기
import { useNavigate } from "react-router-dom";
import { addWishlist } from "../api";
function CoursePage() {
const navigate = useNavigate();
const handleAddWishlistClick = () => {
addWishlist(course?.slug);
navigate("/wishlist");
};
11. react-helmet으로 페이지 제목 설정하기
react-helmet이란?
react-helmet
은 React 애플리케이션에서 <head>
태그를 더 쉽게 조작할 수 있게 해주는 라이브러리입니다.
react-helmet
을 사용하면 React 컴포넌트 내에서 동적으로 <title>
, <meta>
, <link>
등의 태그를 추가, 수정, 제거할 수 있습니다. 이를 통해 SEO(Search Engine Optimization)나 SMO(Social Media Optimization)에 필요한 메타 정보를 쉽게 추가할 수 있습니다.
페이지 제목 설정하기
import { Helmet } from "react-helmet";
function HomePage() {
return (
<>
<Helmet>
<title>덮어 쓸 타이틀</title>
</Helmet>
</>
);
}
- 이런 식으로 컴포넌트 상단에
<Helmet>
태그를 사용하면 기존의 <head>
태그를 overriding할 수 있습니다.
12. 싱글 페이지 애플리케이션(SPA) 이해하기
클라이언트 사이드 렌더링이란?
- 클라이언트 사이드 렌더링(Client-side rendering, CSR)은 클라이언트(브라우저)에서 JavaScript를 사용하여 웹 페이지를 렌더링하는 방식입니다.
- 서버에서 HTML을 생성하는 대신, 서버에서 필요한 데이터만 전송하고 클라이언트에서 페이지를 렌더링하기 때문에 SPA(Single Page Application)에서 많이 사용됩니다.
- CSR을 사용하는 대표적인 프레임워크로는 React, Vue.js, Angular 등이 있습니다. 이러한 프레임워크에서는 CSR을 지원하는 라우터를 제공하여 SPA 개발을 쉽게 할 수 있습니다.
CSR의 장단점
- 클라이언트에서 렌더링되는 컨텐츠가 많기 때문에, 초기 로딩 이후에는 페이지 이동이 매우 빠르고 부드러운 UX를 제공할 수 있습니다.
- 브라우저에서 캐싱을 통해 필요한 파일을 저장하고 재사용할 수 있으므로, 불필요한 네트워크 요청을 줄일 수 있습니다.
- 초기 로딩 시에 필요한 JavaScript 파일을 다운로드하고 실행하기 때문에 초기 로딩 시간이 오래 걸립니다.
- 검색 엔진 최적화(SEO)를 위해서는 검색 엔진 크롤러가 렌더링된 HTML을 읽어야 하는데, CSR에서는 초기 HTML에는 컨텐츠가 없으므로 검색 엔진에서 페이지가 검색되지 않을 수 있습니다. 이를 해결하기 위해서는 검색 엔진을 위한 별도의 처리가 필요합니다.
싱글 페이지 애플리케이션이란?
- 싱글 페이지 애플리케이션(Single Page Application, SPA)은 하나의 HTML 페이지와 애플리케이션 로직을 담당하는 JavaScript 파일로 구성된 웹 애플리케이션입니다.
- SPA는 페이지 전환 시에 HTML을 서버로부터 다시 로딩하지 않고, JavaScript를 사용하여 필요한 데이터만 받아와서 동적으로 페이지를 렌더링합니다.
Feedback
<Outlet>
이 어떤 방식으로 렌더링 되는 지 궁금하다.
- React Router, React 공식 문서를 공부하는 것이 좋은 것 같다.
- 이론적 공부와 실용적인 상황을 연결해서 공부하자.
- Reference에 있는 CSS Modules Stylesheet 관련 글을 따로 쓰자.
Reference