Router

Router

유형
실습문서
주제

레시피앱제작

순번
8
태그
설명

React-Router V6.4에 추가된 함수를 사용합니다.

1. Router 이해 (Part1)

1.1. 시작파일

💡

만약 이전 파일이 없을 경우 아래의 파일을 다운로드 하여 진행한다 파일다운로드

파일활용방법안내

1.2. 연관링크

종류
🔗링크
리액트라우터 공식문서

1.3. 라우터의 기초개념

1.3.1. 라우터란

데이터를 연결하는 장치

1.3.2 라우팅이란

데이터의 최단/최적경로 찾기

image

1.3.3. MPA란

레거시(Classic) 웹페이지의 링크(라우팅)방식. 여러개 페이지를 제작하여 연결하는 방식으로 데이터 변경시 페이지 전체가 업데이트 된다.

1.3.4. SPA란

한개의 페이지에서 변경된 데이터 컴포넌트만 업데이트 되는 방식으로 index 페이지 하나에 컴포넌트를 교체하는 방식으로 제작한다.

데이터 변경시 변경된 데이터만 업데이트 된다.

image

2. 라우터 연습

2.1. react-router 설치

react-router 란 리액트 개발환경에서 리액트의 컴포넌트를 연결하는 모듈이다. v3 까지는 하나의 모듈 이었으나 v4부터 네이티브 앱 용 react-router-native와 웹 개발용 react-router-dom 으로 분리되었다.

React-router-dom v5 VS v6

React-rounter-dom v5 와 v6의 명령어가 크게 변경 되었으므로 프로젝트 유지보수 전 사용된 버전을 확인 후 그에 맞는 문법을 사용해야 한다.

또한 v6도 v6.4이후 추가된 문법이 있다. 예제에서는 v6.4이후를 중점적으로 다룬다.

  1. 모듈설치
  2. 열려있는 개발서버를 닫고 vscode 터미널 창에 아래의 명령어를 입력후 설치한다.
  3. npm install react-router-dom
  4. 설치가 완료되면 서버를 실행한다.
  5. npm start

2.2. 컴포넌트 작성

연습할 컴포넌트 환경을 구성한다.

🔗snippets 설정하기

  1. 아래 구조대로 컴포넌트를 생성한다
  2. /src─┬─
         └──/pages ─┬
                    ├──Home.js
                    └──Products.js
    
  3. Home 과 Projects 컴포넌트를 생성한다.
  4. import React from "react";
    const Home = () => {
      return <h1>Home</h1>;
    };
    export default Home;
    import React from "react";
    const Products = () => {
      return <h1>Products</h1>;
    };
    export default Products;
    🚩 jsx 확장자는 뭔가요?

2.3. 라우터 연결하기

2.3.1. 라우터 설치

라우터(전화기)를 사용하여 라우팅(통화)을 하는 과정을 3단계로 나누어보자.
  1. 👉 전화기를 산다 = 라우터 설치
  2. 통신서비스를 신청한다. = 통신서비스를 중개하는 객체에 서비스 신청
  3. 친구한테 전화건다 = 라우팅 연결
🐨 보충

지금 우리는 여기서 👉 1단계 를 하는 것 이다.

이 단계를 맡고 있는 🔗createBrowserRouter 이라는 객체를 사용해보자.

createBrowserRouter 객체로 특정 컴포넌트를 래핑하게 되면 하위의 컴포넌트는 라우터를 설정 할수있는 다양한 객체들을 상속받아 사용 할 수 있다.

2.3.2. createBrowserRouter 설치

이 API 는 react-router v6.4 에 새로 추가되었다. 🔗Official

라우팅 서비스를 이용할 컴포넌트들은 이 객체로 래핑되어 있어야 한다.

또한 새로 추가된 loaders,actions,fetchers 등의 API를 이용하려면 이 객체로 래핑 되어야 한다.

어차피 앱내의 컴포넌트들은 모두 라우팅 될것이므로 최상위 컴포넌트에 작성하자.

  1. 객체 구성확인
  2. //index.js
    import React from "react";
    import ReactDOM from "react-dom/client";
    import { createBrowserRouter } from "react-router-dom";
    import "./index.css";
    import App from "./App";
    import Home from "./pages/Home";
    console.log(createBrowserRouter([{}]));
    const root = ReactDOM.createRoot(document.querySelector("#root"));
    root.render(
      <>
        <Home />
         <App />
      </>
    );
    image

    콘솔창에서 객체가 가지고 있는 값을 확인해보면 url 정보가 많이 보인다.

    우리는 이제 필요할때마다 이 안의 객체들을 사용해서 리액트의 컴포넌트 연결을 하는 것이다.

  3. 객체를 변수에 할당후 라우터 연결
    1. 👉 1. 전화기를 산다 = 라우터 설치
      1. 통신서비스를 신청한다. = 통신서비스를 중개하는 객체에 서비스 신청
      2. 친구한테 전화건다 = 라우팅 연결
    2. path : 경로
    3. element: 경로접근시 열릴 컴포넌트
    4. const router = createBrowserRouter([
        {
          path: "/",
          element: <Home />,
        },
      ]);

2.3.3. RouterProvider 서비스신청

이 API 는 react-router v6.4 에 새로 추가되었다. 🔗Official

1. 전화기를 산다 = 라우터 설치

👉 2. 통신서비스를 신청한다. = 통신서비스를 중개하는 객체에 서비스 신청

  1. 친구한테 전화건다 = 라우팅 연결

설정한 라우터와 컴포넌트를 연결할때는 provider 컴포넌트를 사용한다

  1. RouterProvider 컴포넌트를 이용한다
  2. import React from "react";
    import ReactDOM from "react-dom/client";
    import { createBrowserRouter, RouterProvider } from "react-router-dom";
    import "./index.css";
    import App from "./App";
    import Home from "./pages/Home";
    const router = createBrowserRouter([
      {
        path: "/",
        element: <Home />,
      },
    ]);
    const root = ReactDOM.createRoot(document.querySelector("#root"));
    root.render(
      <RouterProvider router={router}>
        
        <Home /> <App />
      </RouterProvider>
    );

    RouterProvider 객체는 router속성을 포함하고 있다.

    이 속성에 연결할 컴포넌트의 정보를 전달하면 라우팅이 완료 된다.

  3. 아래와 같이 Home 컴포넌트가 라우팅 된것을 볼수있다.
  4. image
  5. 라우터 추가 작성
  6. //index.js
    
    import Products from "./pages/Products";
    //...중략
    const router = createBrowserRouter([
      {
        path: "/",
        element: <Home />,
      },
      {
        path: "/app",
        element: <App />,
      },
      {
        path: "/products",
        element: <Products />,
      },
    ]);
  7. 브라우저 주소창에 설정한 경로를 입력하면 페이지 새로고침 없이 컴포넌트가 이동한다.
  8. image

2.4. Link

Link 컴포넌트는 html의 a 태그로 변환된다.

href 속성은 to 라는 props로 작성한다.

  1. Home 컴포넌트에 a태그로 링크생성
  2. //Home .js
    import React from "react";
    const Home = () => {
      return (
        <div>
          <h1>Home</h1>
          <a href="/products">Products</a>
        </div>
      );
    };
    export default Home;
  3. 클릭하면 화면이 한번 깜빡이고 이동한다.
  4. image

    이 방법은 페이지 내의 모든 컴포넌트를 새로고침하게 되며 SPA 내의 모든 컴포넌트를 리렌더 하게 된다.

    리액트 앱은 작은 컴포넌트가 모여 큰 페이지를 만든다.

    위의 방법은 변경내역이 없는 컴포넌트 까지 불필요하게 리렌더 되므로 성능저하를 유발한다.

  5. Link 컴포넌트로 변경하자
  6. //Home.js
    import { Link } from "react-router-dom";
    const Home = (props) => {
      return (
        <>
          <h1>Home</h1> 
          <Link to="/products">LINK-Product </Link>
          <a href="/products">Products</a>
        </>
      );
    };
    export default Home;
    a태그는 비교하기 위해 남겨놓았다.
  7. 화면의 새로고침 없이 이동이 되는지 확인한다
  8. image

    a태그로 변환 되었는지도 확인한다.

    image

2.5. children

라우터 설정을 구조화 할수 있는 방법을 알아보자

컴포넌트 간의 관계에 따라 라우터도 계층화 한다면 좋을것이다.

  1. pages 폴더에 모든 컴포넌트를 제어할 RootLayout 컴포넌트를 만든다. 이 컴포넌트는 mpa의 index.html 과 같은 역할을 한다.
  2. //src.js
    import Navi from "./Navi";
    const RootLayout = () => {
      return (
        <>
          <h1>Root</h1> <Navi />
        </>
      );
    };
    export default RootLayout;
  3. Navi 컴포넌트를 작성한다.
  4. ///Navi.js
    import { Link } from "react-router-dom";
    import "./Navi.css";
    const Navi = () => {
      return (
        <ul>
          <li>
            <Link to="/home">Home</Link>
          </li>
          <li>
            <Link to="/products">Products</Link>
          </li>
        </ul>
      );
    };
    export default Navi;
  5. Navi.css 는 아래 코드를 복붙한다.
  6. ul {
      padding: 1rem;
      display: flex;
      position: fixed;
      bottom: 0;
      list-style: none;
      gap: 2rem;
      background-color: pink;
    }
  7. index.js 로 이동하여 router 의 속성을 추가한다.
  8. 이때 헷갈리므로 root.render 함수는 RouterProvider 만 남겨놓는다

    import RootLayout from './pages/RootLayout';
    const router = createBrowserRouter([
       {
          path: '/',
          element: <RootLayout />,
          children: [
             {
                path: 'products',
                element: <Products />,
             },
             {
                path: 'home',
                element: <Home />,
             },
          ],
       },
    ]);
    ...
    root.render(<RouterProvider router={router} />);

    브라우저에서 테스트 해보면 경로의 이동은 되는데 컴포넌트의 렌더링이 되지 않는다.

    라우터 설정은 잘 되었으나 RootLayout 에서 Home과 Products 컴포넌트를 임포트 하지 않았기 때문이다.

    리액트의 컴포넌트는 이점이 굉장히 불편하다. GNB나 모달팝업, 패널메뉴 같이 모든 컴포넌트에 표시돼야 하는 UI의 경우 매번 임포트해야 한다.

    하지만 최상위 컴포넌트를 만들어서 제어한다면 최상위에서 Outlet 이라는 컴포넌트만 작성하면 해결할수 있다.

2.6. Outlet

중첩된 라우트를 렌더링하는 데 사용되는 컴포넌트

React Router v6부터 도입된 Outlet은 부모 라우트 내에서 자식 라우트가 렌더링될 위치를 지정하는 역할을 한다.

  1. RootLayout 에 Outlet 을 임포트 한다.
  2. //RootLayout.js
    
    import { Outlet } from "react-router-dom";
    import Navi from "./Navi";
    const RootLayout = () => {
      return (
        <>
            <h1>Root</h1> <Navi /> 
            <Outlet />
        </>
      );
    };
    export default RootLayout;

브라우저에서 테스트 해보면 Root와 Navi 는 모든 컴포넌트에서 표시되며 컴포넌트간 이동도 잘 되는것이 확인된다.

2.7.  errorElement

react-router 에서 자체적으로 제공하는 예외처리문은 여러개가 있다.

그중 에러상황 발생시 사용할수 있는 객체를 알아보자 🔗

  1. pages/Error.js 를 만들고 404 메시지를 작성한다.
  2. //pages/Error.js
    
    import "./Error.css";
    import Navi from "./Navi";
    const ErrorPage = (props) => {
      return (
        <>
          <Navi /> <div className="error">404Error</div>
        </>
      );
    };
    export default ErrorPage;
    /*Error.css*/
    .error {
      width: 100vw;
      height: 100vh;
      font-size: 5vw;
      color: white;
      background-color: #ff0000;
    }
  3. 라우터 설정 추가
  4. //index.js
    import ErrorPage from './pages/Error';
       {
          path: '/',
          element: <RootLayout />,
          errorElement: <ErrorPage />,
          children: [
             {
                path: 'products',
                element: <Products />,
             },
             {
                path: 'home',
                element: <Home />,
             },
          ],
       },
    ]);
    
  5. 주소창에 없는 경로를 입력하면 에러컴포넌트가 확인된다
  6. image

2.8. useNavigate

웹페이지 개발시 a태그를 사용하지 않고 자바스크립트를 사용하여 링크를 해야 하는 경우가 있다.

이런 상황을 리액트에서는 어떻게 해결할수 있을까?

useNavigate 훅은 이펙트 등에서 프로그래밍 방식으로 탐색할 수 있는 함수를 반환한다.

React 애플리케이션에서 페이지 간의 탐색(navigation)을 수행하는 훅이다.

  1. 아래와 같이 코딩한다
  2. //src.js
    import { Link, useNavigate } from "react-router-dom";
    const Home = (props) => {
      const navi = useNavigate();
      const naviFn = () => {
        navi("/App");
      };
      return (
        <>
          <h1>Home</h1>
          <button onClick={naviFn}>click</button>
          <Link to="/products">LINK-Product</Link> <a href="/products">Products</a>
        </>
      );
    };
    export default Home;
  3. index.js 로 이동하여 라우터를 설정한다.
  4. //src\index.js
    const router = createBrowserRouter([
    {
       //...중략
    },
    {
     path:'/app',
     element: <App />,
    }

    버튼을 클릭하면 App 컴포넌트가 렌더링된다.

    image

    이 코드는 링크 컴포넌트를 사용하여 이동하는 것이 아닌 버튼을 클릭시 함수를 호출하고 그 함수에서 다른 컴포넌트로 이동하기 위한 네비게이션 훅을 호출하는 방식이다.

2. useParams (Part2)

2.9. 동적라우팅(useParams)

📢 시나리오:

상품페이지에는 여러개의 상품이 있다.

각 상품별 데이터가 다르므로 상품상세페이지는 상품마다 각각 라우터를 작성해야 한다.

이럴 경우 상품의 고유 식별자와 useParams를 활용하여 라우팅 하면 편리하다.

🐨

useParams 란?

의미를 풀어보면 다음과 같다 (☞゚ヮ゚)☞ use(사용하다)+Params(매개변수) = useParams(매개변수를 사용하다)

  1. src\pages\Products.js 에 구조를 추가하자
  2. //src\pages\Products.js
    
    import React from 'react';
    
    const Products = () => {
     return (
     	<>
     		<h1>Products</h1>
     		<ul>
     			<li>상품1</li>
     			<li>상품2</li>
     			<li>상품3</li>
     		</ul>
     	</>
     );
    };
    export default Products;
  3. 상품상세페이지 컴포넌트를 만든다
  4. //src\pages\ProductDetail.js
    
    const ProductDetail = () => {
     return <div>ProductDetail</div>;
    };
    export default ProductDetail;
  5. 라우터를 작성한다
  6. //src\index.js
    
    import React from 'react';
       import ReactDOM from 'react-dom/client';
       import { createBrowserRouter, RouterProvider } from 'react-router-dom';
       import './index.css';
       import App from './App';
       import Home from './pages/Home';
       import Products from './pages/Products';
       import ProductDetail from './pages/ProductDetail';
       import RootLayout from './pages/RootLayout';
       import ErrorPage from './pages/Error';
       const router = createBrowserRouter([
          {
             path: '/',
             element: <RootLayout />,
             errorElement: <ErrorPage />,
             children: [
                {
                   path: 'products',
                   element: <Products />,
                },
                {
                   path: 'products/detail',
                   element: <ProductDetail />,
                },
                {
                   path: 'home',
                   element: <Home />,
                },
             ],
          },
          {
             path: '/app',
             element: <App />,
          },
       ]);
    // ...

    상품의 아이디나 관리번호에 따라 path 는 변경되야 할것이다

  7. 동적경로 작성하기 :
  8. //src\index.js
    
    { path: '/products/:id', element: <ProductDetail /> },

    : 이후의 값은 동적으로 처리된다

  9. 확인
  10. 숫자 5를 넣어도 apple을 넣어도 상세 컴포넌트가 렌더링 된다

    image
  11. 상세 페이지에서 parameter 을 받아보자
  12. 전달하는 parameter 을 받을때는 useParams 가 필요하다

    //src\pages\ProductDetail.js
    
    import { useParams } from 'react-router-dom';
    
    const ProductDetail = (props) => {
    	console.log(useParams());
    
    	return (
    		<>
    			<div>ProductDetail</div>
    		</>
    	);
    };
    export default ProductDetail;
  13. 확인
  14. image
  15. id 저장
  16. const {id}=useParams()
      // ...
     <div>{id}</div>

2.10.동적링크

Link 컴포넌트의 to props를 동적으로 처리해보자
  1. src\pages\Products.js 로 이동후 Link 컴포넌트를 추가한다
  2. 상품들은 일반적으로 백엔드 서버에서 받아오겠지만 지금은 상수로 작성한후 map으로 불러온다
  3. Link to 에 템플릿리터럴을 사용하여 속성을 전달하면 동적으로 링크주소를 작성할수 있다
  4. //src/pages/Products.js
    
    import { Link } from 'react-router-dom';
    const PRODUCTS = [
     { id: '1', title: '상품1' },
     { id: '2', title: '상품2' },
     { id: '3', title: '상품3' },
    ];
    const Products = () => {
     return (
     	<>
     		<h1>Products</h1>
     		<ul>
     			{PRODUCTS.map((item) => (
     				<li key={item.id}>
     					<Link to={`/products/${item.id}`}>{item.title}</Link>
     				</li>
     			))}
     		</ul>
     	</>
     );
    };
    export default Products;
  5. 짜잔
  6. image

2.11. Router Path

createBrowserRouter 함수가 반환하는 여러 객체중에는 경로 설정 키인 path의 속성에 다양한 옵션을 추가할수 있다

2.11.1. 상대경로

💡

router 의 children 배열의 path 속성에서 / 를 삭제 한다는것은 해당 경로를 상대경로로 변경한다는 의미이다.

//src/index.js

import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import Home from './pages/Home';
import Products from './pages/Products';
import ProductDetail from './pages/ProductDetail';
import RootLayout from './pages/Root';
import ErrorPage from './pages/Error';

const router = createBrowserRouter([
	{
		path: '/',
		element: <RootLayout />,
		errorElement: <ErrorPage />,
		children: [
			{ path: '', element: <Home /> },
			{ path: 'products', element: <Products /> },
			{ path: 'products/:id', element: <ProductDetail /> },
		],
	},
]);

const App = () => {
	return <RouterProvider router={router} />;
};
export default App;
image
image

product.js 의 Link 를 보자

image

상대경로로 수정한다

image
image
//src\pages\Products.js

//...
{PRODUCTS.map((item) => (
	<li key={item.id}>
		<Link to={item.id}>{item.title}</Link>
	</li>
//...
💡

src\pages\ProductDetail.js 에 뒤로가기 버튼을 상대 경로로 넣어보자

//src\pages\ProductDetail.js

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

const ProductDetail = () => {
	const params = useParams();
	return (
		<div>
			<h1>ProductDetail</h1>
			<div>{params.id}</div>
			<div>
				<Link to='..'>Back</Link>
			</div>
		</div>
	);
};
export default ProductDetail;
image
image

상품 상세 페이지에서 상품 페이지로 이동 하는 것이 아니라 바로 홈으로 이동한다.

이유는 상세페이지 와 상품 페이지는 형제 관계이기 때문이다

수정해보자

//src\pages\ProductDetail.js
//...
<div><Link to=".." relative="path">Back</Link></div>

relative 속성에 path 를 사용하면 path 단위로 이동이 가능하다

2.11.2. index 라우트

메인 컴포넌트에 설정할수 있는 index 속성을 사용해보자
//src\index.js

import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import Home from './pages/Home';
import Products from './pages/Products';
import ProductDetail from './pages/ProductDetail';
import RootLayout from './pages/Root';
import ErrorPage from './pages/Error';

const router = createBrowserRouter([
	{
		path: '/',
		element: <RootLayout />,
		errorElement: <ErrorPage />,
		children: [
			{ index: true, element: <Home /> },
			{ path: 'products', element: <Products /> },
			{ path: 'products/:id', element: <ProductDetail /> },
		],
	},
]);

const App = () => {
	return <RouterProvider router={router} />;
};
export default App;

이 키는 빈 path 대신 메인 컴포넌트를 지정할수 있는 속성이다.

//src/index.js

import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import Home from './pages/Home';
import Products from './pages/Products';
import ProductDetail from './pages/ProductDetail';
import RootLayout from './pages/Root';
import ErrorPage from './pages/Error';

const router = createBrowserRouter([
	{
		path: '/',
		element: <RootLayout />,
		errorElement: <ErrorPage />,
		children: [
			{ index: true, element: <Home /> },
			{ path: 'products', element: <Products /> },
			{ path: 'products/:id', element: <ProductDetail /> },
		],
	},
]);

const App = () => {
	return <RouterProvider router={router} />;
};
export default App;

3. 완료파일

08.zip7.3KB