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