[React 완벽 가이드] Section 21: Routing _SPA 다중 페이지 구축

gonn-i·2024년 7월 13일
0

React 완벽 가이드

목록 보기
16/18
post-thumbnail

본 포스트는 Udemy 리액트 완벽가이드 2024 를 듣고 정리한 내용입니다.

목차 🌳
1️⃣ Route와 SPA 🤨
2️⃣ react-Router 사용하기 📁
3️⃣ router를 이용한 data Fetching과 submite 📥📤

Route와 SPA 🤨

Routing 은 네트워크에서 경로를 선택하는 프로세스를 의미
즉, 주소에 대한 요청이 들어왔을때 상응하는 content 로 이동시켜주는 작업

Route란!
URL 경로에 따라 다른 콘텐츠를 보여주기 위해 설정된 규칙

Multiple Page Application 에서는,
새로운 페이지를 요청할 때마다 서버에서 렌더링된 정적 리소스(HTML, CSS, JavaScript)를 응답으로 받아오는 방식이다. 따라서, 페이지 이동하거나 새로고침하면 전체 페이지를 다시 렌더링했기에 interface를 가져오는 과정에서 사용자 경험이 떨어진다. (로딩~.. 지연 .. 화면 깜빡임)

그래서 react에서는 SPA (single page application) 을 사용하는데!

Single Page Application 에서는,
최초의 HTML 요청을 하나만 전송하고 (단, 하나의 리소스), 페이지 이동 시 기존 페이지의 내부를 수정해서 보여주는 방식이다. Client Side Rendering 으로 랜더링되기 때문에, 간단히 URL 을 감시하다가 그 URL 이 변경되면 다른 리액트 컴포넌트를 로딩할 수 있다.


react-Router 사용하기 📁

0️⃣ 패키지 설치

npm i react-router-dom

1️⃣ 최상위 컴포넌트에서 router 연결 및 라우트 설정

본 강의에서는 v6 에서 지원하는 createBrowserRouter 를 설명
참고용 v5 react router dom

👀createBrowserRouter 안에 path와 element를 담은 객체 배열을 전달해준다.

createBrowserRouter([
  {path: '/', element: <Example />},
  {path: '/list', element: <List />}, ... ])

👀 RouterRrovier 에서 router prop으로 createBrowserRouter 값을 전달해줌

index.js

//index.js 나 app.js 관계없음
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import HomePage from './pages/Home';

// https://example.com/products
// https -> 프로토콜
// example.com -> 도메인
// 도메인 뒤에 부터가 path임

const router = createBrowserRouter([
  { path: '/', element: <HomePage /> }, // 도메인 뒤에 '/' 그리고 아무것도 없으면 path: '/' 가 실행
  {},
]);

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <RouterProvider router={router}></RouterProvider>
  </React.StrictMode>
);

1️⃣-1 또 다른 방식으로 router 하는 방법

createBrowserRouter 에 배열 객체를 넘겨주지 말고,
createRoutesFromElement 을 통해 중첩 route 를 설정해주고 반환된 값을 넘겨주는 방법도 있다!

const routeDefinitions = createRoutesFromElements(
  <Route>
    <Route path="/" element={<HomePage />}></Route>
    <Route path="/products" element={<Products />}></Route>
  </Route>
);

const router = createBrowserRouter(routeDefinitions);

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <RouterProvider router={router}></RouterProvider>
  </React.StrictMode>
);

// 이전까지 URL 이동시킨 방법
<p>
  go to
  <a href="/products">제품 페이지로 이동</a>
</p>

이전까진 우린 url 을 이동시키기 위해서, a 태그에 href 속성을 이용해왔다. 그치만, a 태그를 통한 이동은 서버에 새로운 요청을 전송하게 되고 이로 인해 모든 리액트 애플리케이션을 재랜더링 하게 된다!!! 이렇게 되면 SPA 쓰는 이유가 없음

따라서 우리는 react-router-domLink를 이용할 것이다

LinkHTTP 요청 전송을 막아주고, Route 설정에 맞춰 to 속성을 통해 변경된 URL 에 따라 적절한 콘텐츠를 업데이트 해준다.

import { Link } from 'react-router-dom';


// Link 는 요소에 대한 클릭을 감시하여, 클릭시 Http 요청을 전송하는 브라우저의 기본 설정을 막아주며
// 대신 라우트 정의를 확인하여 그에 맞춰 페이지를 업데이트하게 된다
function HomePage() {
  return (
    <>
      <h1> 홈페이지입니다 🏠 </h1>
      <p>
        go to
        <Link to="/products">제품 페이지로 이동</Link>
      </p>
    </>
  );
}

export default HomePage;

3️⃣ 레이아웃 및 중첩된 라우트 관리

Header에 위치한 Nav Bar 와 같은 경우, RootLayout Component로 만들어 공통적으로 Nav가 필요한 route(페이지)에 부모 route 로서 감싸주어야 한다.

이때 children 속성Nav를 포함한 RootLayout 을 필요로 하는, 자식 라우트들을 넣어주면 된다.

// index.js
const router = createBrowserRouter([
  {
    path: '/',
    element: <RootLayout />,
    children: [ // children 배열 안에 중첩될 라우트를 넣어준다.
      { path: '/', element: <HomePage /> }, // 도메인 뒤에 '/' 그리고 아무것도 없으면 path: '/' 가 실행
      { path: '/products', element: <Products /> },
    ],
  },
]);

부모 라우트는 wrapper 역할을 하는데, 이에 대해 자녀 라우트 요소가 어디에서 자리잡을지를 Outlet 컴포넌트로 정해줘야 한다. (자식 요소의 랜더링 장소 표시)

import MainNav from '../components/MainNav';
import { Outlet } from 'react-router-dom';
import classes from './Root.module.css';

function RootLayout() {
  return (
    <>
      <MainNav />
      <main className={classes.content}>
        <Outlet /> // 중첩 라우트의 요소 위치 
      </main>
    </>
  );
}

export default RootLayout;

4️⃣ errorElement 를 통한 오류 페이지 표시

잘못된 URL 을 입력할 경우, react-router-dom 에서는 자체적으로 다음과 같이 오류를 throw 해준다.

이때 개발자는 사용자 경험을 높이기 위해 404 에러를 다루는 페이지를 구성한다.


(구글의 깔꼼한 404 처리 )

아무튼 react-router-dom에서 errorElement 에 컴포넌트를 연결해줌으로써 404 처리를 해줄 수 있다!

const router = createBrowserRouter([
  {
    path: '/',
    element: <RootLayout />,
    errorElement: <Error />, // 에러를 핸들링할 요소 ⭐️
    children: [
      { path: '/', element: <HomePage /> }, // 도메인 뒤에 '/' 그리고 아무것도 없으면 path: '/' 가 실행
      { path: '/products', element: <Products /> },
    ],
  },
]);

Link 컴포넌트가 가장 기본적인 네비게이션 링크를 생성한다면,

NavLinkLink 컴포넌트의 기능을 확장하여, 현재 경로와 일치하는 경우 링크를 활성화된 상태로 만들 수 있는 기능을 추가하고 있다.

동적으로 스타일 추가하고 싶으면 NavLink
그냥 기본적인 네비게이션 쓰고 싶으면 Link


<NavLink to="/" className={({ isActive }) => (isActive ? classes.active : undefined)} end>
  HOME 🏠
</NavLink>

isActive 속성을 통해, 현재 경로와 일치하는 경로에 대해 특별한 스타일이나 클래스 적용 가능

또한, 본래는 링크의 경로가 현재 경로의 접두사와 일치하는 경우에도 활성화가 되었지만!
end 속성을 통해, to 속성에 지정된 경로와 현재 경로가 완전히 일치할때에만 링크가 활성화되게 설정할 수 있다!


6️⃣ useNavigate 으로, 특정 조건이나 이벤트에 따라 경로 변경하기

네브바에서 링크를 클릭해서 이동하는 경우가 아니라, 폼 제출 후 리다이렉션이나 조건에 따라 리다이렉션을 줘야 하는 프로그램적으로 네이비게이션을 주는 경우 useNavigate 훅을 사용한다.

import { useNavigate } from 'react-router-dom';

function HomePage() {
  const navigation = useNavigate();

  const handleClick = () => {
    navigation('/products');
  };
  return (
    <>
      <h1> 홈페이지입니다 🏠 </h1>
      <p>
        <button onClick={handleClick}>상품 보기 가기🏃🏻‍♀️</button>
      </p>
    </>
  );
}

7️⃣ 동적 라우트 설정

쇼핑몰 사이트를 제작한다고 가정할때, 우린 상품 리스트 중 하나를 누르면 그 상품에 대한 URL로 이동을 하게 된다. 이때 상품이 10개이고, 100개이고, 1000개일때 우린 일일이 주소를 route 하지 않는다!!

/:식별자 형식으로 콜론 뒤에 원하는 식별자를 넣어 매개변수(params)를 설정해줄 수 있다!

index.js

const router = createBrowserRouter([
  {
    path: '/',
    element: <RootLayout />,
    errorElement: <Error />, // 에러를 핸들링할 요소
    children: [
      { path: '/', element: <HomePage /> }, // 도메인 뒤에 '/' 그리고 아무것도 없으면 path: '/' 가 실행
      { path: '/products', element: <Products /> },
       // 콜론을 통해 경로 매개변수 받기 ⭐️
      { path: '/product/:productId', element: <ProductDetail /> }, 
    ],
  },
]);

Products.jsx

import { Link } from 'react-router-dom';

const PRODUCTS = [
  { id: 'p1', title: '제품1' },
  { id: 'p2', title: '제품2' },
  { id: 'p3', title: '제품3' },
];

function Products() {
  return (
    <div>
      <h1> 제품 페이지입니다 🛒</h1>
      <ul>
        {PRODUCTS.map((prod) => (
          <li>
            <Link to={`/product/${prod.id}`}>{prod.title}</Link>
          </li>
        ))}
      </ul>
    </div>
  );
}

ProductDetail.jsx

import { useParams } from 'react-router-dom';

function ProductDetail() {
  const params = useParams(); // 1.useParams 훅 실행
  
  //2. /:productId -> params.productId 로 꺼내오기
  return (
    <>
      <h1>제품 상세 페이지</h1>
      <p> {params.productId}</p>
    </>
  );
}


+ 절대경로와 상대경로의 차이점

절대경로: 도메인 루트를 기준으로 명확하게 경로를 지정. 경로는 항상 /로 시작
상대경로: 현재 경로를 기준으로 상대적으로 이동하는 경로를 지정. 경로는 현재 위치에 따라 달라짐

import React from 'react';
import { useNavigate } from 'react-router-dom';

const ProductNavigation = () => {
  const navigate = useNavigate();

  const goToHomeAbsolute = () => {
    navigate('/'); // 절대경로
  };

  const goToHomeRelative = () => {
    navigate('..'); // 상대경로: 현재 경로의 부모 경로로 이동
  };

  return (
    <div>
      <button onClick={goToHomeAbsolute}>Go to Home </button>
      <button onClick={goToHomeRelative}>Go to Parent</button>
    </div>
  );
};

export default ProductNavigation;

router를 이용한 data Fetching과 submite 📥 📤

1️⃣ loaderdata Fetching 📥

loader 는 데이터 로딩을 route 와 통합하여, 라우팅이 발생할때! 함께 데이터도 로드할 수 있도록 한다. (특정 URL로 라우팅이 발생할때, 데이터를 받아온 후에, 컴포넌트를 랜더링)

[loading 과정]
각 라우트에 대해서, 전환이 개시되자마자 데이터 가져오기가 시작되고,
라우터는 loader가 작업을 완료할때까지 대기 후, 가져온 데이터로 페이지를 랜더링한다.

0개의 댓글