React Router로 웹사이트 만들기

Jeris·2023년 4월 27일
0

코드잇 부트캠프 0기

목록 보기
67/107

1. 리액트 라우터 v6 소개

React Router란?

  • React RouterReact 컴포넌트로 페이지를 나누는 라우팅 라이브러리입니다.
  • 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 routingRouter를 만들고 Link<Form>으로 페이지끼리 연결하여 사용할 수 있습니다.
  • Nested Routes
    • React Router v4 이후부터 도입된 개념으로, 하나의 라우터 안에 다른 라우터를 중첩해서 사용하는 것을 의미합니다.
    • 하나의 페이지 내에서 여러 개의 라우트를 관리할 수 있게 해줍니다.
    • 시각화
  • Dynamic Segments
    • URL segmentsdynamic(동적인) 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를 리턴할 수 있습니다.
    • CancellationAbort Signals로 수행되고, Search ParamsURLSearchParams로 처리되며, data mutationsHTML 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 안에 컴포넌트들을 넣어야합니다.
    • BrowserRouterHashRouter 등의 서브클래스가 있으며, 사용하고자 하는 브라우저 히스토리 API에 따라 선택하여 사용할 수 있습니다.
  • Routes
    • Route 컴포넌트들을 정의하고 렌더링하는 컴포넌트입니다.
    • 이 컴포넌트는 여러 개의 Route 컴포넌트를 포함할 수 있으며, Route 컴포넌트에 대한 매칭 규칙을 지정할 수 있습니다.
  • Route
    • URL 경로와 매칭되는 컴포넌트를 정의하는 컴포넌트입니다.
    • path 프로퍼티를 사용하여 URL 경로를 정의하고, component 프로퍼티를 사용하여 매칭될 때 렌더링될 컴포넌트를 지정할 수 있습니다. 또한, exact 프로퍼티를 사용하여 정확한 매칭을 지정할 수 있습니다.
  • Link
    • 다른 경로로 이동하는 링크를 생성하는 컴포넌트입니다.
    • 이를 통해 브라우저 히스토리를 업데이트하고, 브라우저를 다른 경로로 이동시킬 수 있습니다.
    • to 프로퍼티를 사용하여 이동할 경로를 지정할 수 있으며, href 속성이 자동으로 추가됩니다.

2. Routes로 페이지 나누기

// Main.js
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로 이동하기

// components/Nav.js
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로 네비게이션 구현하기

// components/Nav.js
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. 하위 페이지 나누기

// Main.js
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;

// components/App.js
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을 사용하여 라우트 컴포넌트에서 접근할 수 있습니다.
// Main.js
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>
  );
}


// pages/CoursePage.js
import { useParams } from "react-router-dom";
import { getCourseBySlug } from "../api";
...

function CoursePage() {
  const { courseSlug } = useParams();
  const course = getCourseBySlug(courseSlug);
  ...
}


// pages/QuestionPage.js
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로 변경하는 기능을 말합니다.
  • React Router v6에서 도입된 컴포넌트 중 하나로, to prop을 사용하여 다른 경로로 사용자를 리디렉션할 수 있습니다.
// pages/CoursePage.js
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의 쿼리 파라미터를 읽거나 변경하여 다른 컴포넌트에서 사용할 수 있는 객체를 리턴할 수 있게 해줍니다.

쿼리 사용하기

// pages/CouseListPage.js
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)에 필요한 메타 정보를 쉽게 추가할 수 있습니다.

페이지 제목 설정하기

// pages/HomePage.js
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

profile
job's done

0개의 댓글