React Router 알아보기 -React Router로 다중페이지 만들기-

김민규·2023년 1월 9일
2
post-thumbnail

라우팅이란?

라우팅이란 네트워크에서 경로를 선택하는 프로세스이다. 컴퓨터 네트워크는 노드라고 하는 여러 시스템과 이러한 노드를 연결하는 링크로 구성된다. 상호 연결된 네트워크에서 노드 간의 통신은 여러 경로를 통해 이루어질 수 있는데, 라우팅은 이를 정해진 규칙에서 최상의 경로를 찾는 프로세스이다.

React Router에서의 Client Side Routing

React Router는 Client Side Routing을 가능하게 한다.
기존의 웹사이트들이 사용하던 Server Side Routing은 서버에 HTTP를 요청하여 새로운 엔드포인트를 설정하고 CSS 및 JS를 다운로드하고 받은 HTML을 렌더링하였다. 그리고 사용자가 링크를 클릭할 시 그에 따른 새로운 프로세스를 다시 시작한다.

하지만 Client Side Routing을 사용할 경우 서버에 새로운 요청 없이 링크를 클릭하여 가상의 엔드포인트로 URL을 업데이트 할 수 있다. 그리고 어플리케이션은 새로운 UI를 렌더링하고 데이터 요청을 통해 페이지를 새롭게 업데이트 시킨다.

기존의 Server Side Routing처럼 새로운 CSS와 JS를 다운받고 다시 해석하는 과정을 거치지 않기 때문에 훨씬 빠른 사용자 경험을 가능하게 한다.

이는 한 어플리케이션에서 하나의 HTML만을 받아서 실행시키므로 페이지간 분기점이 생기지만 Single Page Application의 근복적인 동작 원리에는 영향을 끼치지 않는다.

또한 SPA를 사용하면서도 URL을 통한 엔드포인트 설정이 가능해져 어플리케이션의 특정 페이지를 URL을 통해 바로 접근할 수 있다는 이점도 생긴다.

React Router 시작하기 (v5)

프로젝트에 React Router 설치

React Router를 시작하기 위해선 React Router를 우선적으로 설치해주어야한다.

터미널을 통해 프로젝트 디렉토리 루트로 이동하여 npm 혹은 yarn을 사용하여 인스톨해준다.

$ npm install react-router-dom

or

$ yarn add react-router-dom

특정 버전의 react router를 설치하고 싶다면 끝에 공백 없이 @version을 입력해준다.

$ npm install react-router-dom@5

이후 pakage.json을 보면 react router가 설치된 걸 확인 할 수 있다.

  "dependencies": {
    // ...
    "react": "^18.0.0",
    "react-dom": "^18.0.0",
    "react-router-dom": "^5.3.4",
    // ...
  },

프로젝트에 React Router 적용

우선 어플리케이션의 최상위 컴포넌트인 APP을 react router의 내부 컴포넌트인 BrowserRouter로 감싸주어야한다.

// index.js

import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router-dom";

import "./index.css";
import App from "./App";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>
);

AppBrowserRouter로 감싸면서 우리는 프로젝트의 페이지들을 URL의 경로에 따라 페이지를 렌더링 할 수 있도록 설정할 수 있다.

컴포넌트에 Route로 라우팅 시키기

어플리케이션을 감싼 이후에는 react router 또다른 내부 컴포넌트인 Route를 통하여 경로에 따라 불러올 컴포넌트를 지정할 수 있다.

// App.js
import { Route } from "react-router-dom";
import Welcome from "./components/Welcome";

function App() {
  return (
    <div>
      <Route path="/welcome">
        <Welcome />
      </Route>
    </div>
  );
}

export default App;

이때 Route 컴포넌트에 특별한 prop을 설정해주어야 하는데 바로 path prop이다.

path prop은 문자열을 값으로 가지며 이때 부여된 값은 Route의 내부 컴포넌들을 렌더링할 조건인 url 경로를 의미한다.

예시로 적힌 위 코드의 경우 http://domain.com/welcome에 접속할 시 해당 컴포넌트들이 렌더링된다.

또한 이러한 방식으로 렌더링되는 페이지 컴포넌트 같은 경우에는 프로젝트의 디렉토리에서 /pages 같은 폴더를 생성하여 일반 컴포넌트들과 분리시키는 것이 권장된다.

링크를 통해 페이지 이동시키기

기존의 HTML에서 링크를 통해 페이지를 이동시킬 때는 <a href="url"> 태그를 이용하였다.

// MainHeader.js

const MainHeader = () => {
  return (
    <header>
      <nav>
        <ul>
          <li>
            <a to="/welcome">Wecome</a>
          </li>
          <li>
            <a to="/products">Products</a>
          </li>
        </ul>
      </nav>
    </header>
  );
};

이는 react router에서도 유효하며 작동한다. 하지만 이는 치명적 결함이 존재하는데 바로 동일한 HTML 내에서 렌더링 되는 것이 아닌 새로운 HTML을 다운받아 렌더링 된다는 점이다.

이는 개발자 도구의 Network에서도 확인 할 수 있다.

이러한 현상을 방지하기 위해서는 react router 내부 컴포넌트인 <Link>를 사용해야 한다. 기존에 <a> 태그를 사용하던 부분에 대치하여 적용시킨 후 href='url' 어트리뷰트를 경로값은 동일한 채로to='url' prop으로 치환한다.

<Link> 컴포넌트를 사용할 경우 HTML 상에서는 동일한 <a> 태그로 렌더링되지만 서버 통신을 통해 페이지가 새로 불러지는 것을 방지한다.

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

const MainHeader = () => {
  return (
    <header>
      <nav>
        <ul>
          <li>
            <Link to="/welcome">Wecome</Link>
          </li>
          <li>
            <Link to="/products">Products</Link>
          </li>
        </ul>
      </nav>
    </header>
  );
};

export default MainHeader;


HTML 상에서는 여전히 앵커 태그로 보인다.

대부분의 어플리케이션의 경우 해당 어플리케이션의 네비게이션 역할을 하는 컴포넌트가 존재한다. 이때 사용자가 위치한 페이지를 네비게이션에서 강조하여 표시하는 경우를 본 적이 있을 것이다.


ex) 에어비앤비의 숙소 타입 네비게이션 컴포넌트

react route에서는 이러한 케이스를 위하여 기존의 <Link> 컴포넌트에 약간의 기능을 추가한 <NavLink> 컴포넌트가 존재한다.

<NavLink> 컴포넌트의 경우 activeClassName prop을 추가로 받는데 이 prop에 할당되는 값을 해당 <Link> 컴포넌트가 활성화(active) 되었을때 class로서 부여한다.


//...
  <li>
    <NavLink to="/welcome" activeClassName='active시 추가할 class'>
      Wecome
    </NavLink>
  </li>
  <li>
    <NavLink to="/products" activeClassName='active시 추가할 class'>
      Products  
    </NavLink>
  </li>
//...

동적 라우팅(Dynamic Routing)

만약에 우리가 쇼핑몰을 제작한다고 가정해보자.
그렇다면 프론트엔드 개발자는 상품이 나열되는 상품 리스트 컴포넌트를 만들 것이고 상품 리스트 내에 존재하는 상품을 클릭시 상품의 상세 페이지로 이동하도록 구현 할 것이다.

이때 react router를 사용한다면 지금까지 알아본 내용과 같이 <Link> 컴포넌트를 통해 라우팅되는 상세 페이지를 렌더링 할 것이다.

그렇다면 여기서 상품에 대한 데이터를 어떻게 라우팅 된 페이지에게 전달 할 수 있을까?

실제 쇼핑몰을 통해 한번 알아보자.


오늘의 집의 상품 상세 페이지

사진의 적힌 url을 보면 url 내부에 상품에 대한 id 코드가 존재하는 걸 볼 수 있다.

즉, 페이지가 특정한 id를 받아서 렌더링을 실행 했다고 볼 수 있는것이다.

이처럼 라우팅 과정에서 페이지에 유동적인 값(Path Parameter)을 부여하여 라우팅 하는 것을 동적 라우팅(Dynamic Routing)이라고 한다.

동적 라우팅을 설정하는 방법은 다음과 같다.

// ...
    <Route path="/welcome">
      <Welcome />
    </Route>
    <Route path="/products">
      <Products />
    </Route>
    <Route path="/products/:productId">
      <ProductDetail />
    </Route>
//...

기존에 존재하던 Route 컴포넌트에 부여한 path:를 부여하면 이는 뒤에 Path Parameter가 입력된다는 것을 의미한다.

:를 통해 동적 라우팅을 지정한 이후에는 url에 어떤 값이 오든 기본적으로 동적 라우팅 페이지 내의 컴포넌트들을 렌더링한다.

동적 라우팅 경로 구성하기(v5 기준)

이 포스트는 react route v5를 기준으로 작성되었습니다.

이제 동적 라우팅 되는 페이지를 Link 해주는 컴포넌트를 보자.

// ProductDetail.js

// ...
    <li>
      <Link to="/products/p1">Product1</Link>
    </li>
// ...

위에 코드에 의하면 해당 <Link>를 클릭 할 시 사용자는 domain.com/products/p1으로 이동 할 것이다.

하지만 실제 페이지에는 ProductDetail 컴포넌트 뿐만이 아닌 Products 컴포넌트 또한 렌더링 될 것이다.

어째서일까? 페이지를 라우팅하는 App.js를 다시 보자.

// App.js

function App() {
  return (
    <div>
      <MainHeader />
      <main>
        <Route path="/welcome">
          <Welcome />
        </Route>
        <Route path="/products">
          <Products />
        </Route>
        <Route path="/products/:productId">
          <ProductDetail />
        </Route>
      </main>
    </div>
  );
}

App.js의 내부를 보면 /products를 경로로 갖는 라우트 페이지가 <ProductDetail><Products> 총 2개 존재하는 것을 볼 수 있다.

따라서 경로가 포함된 페이지를 모두 렌더링했기에 이런 결과가 나온 것이다.

이런한 다중 렌더링을 막기 위해서 react router에는 <Switch> 컴포넌트가 존재한다.

<Switch> 컴포넌트 내부에 위치한 라우트 페이지 들은 오로지 경로 하나에 페이지 하나만을 렌더링 하도록 렌더링 로직을 변경시킨다.

// App.js

function App() {
 return (
   <div>
     <MainHeader />
     <main>
   	<Switch>
         <Route path="/welcome">
           <Welcome />
         </Route>
         <Route path="/products">
           <Products />
         </Route>
         <Route path="/products/:productId">
           <ProductDetail />
         </Route>
   	</Switch>
     </main>
   </div>
 );
}

하지만 이번에는 <ProductDetail> 렌더링 되지 않는 문제가 발생한다. url 경로 상에는 분명히 값이 주어졌는데도 말이다.

물론 현재 경로와 더 일치하는 <ProductDetail>이 렌더링 된다 생각할 수도 있다.
하지만 react route는 경로의 일치 정도는 신경쓰지 않고 코드를 위에서 아래로 읽으면서 지정된 경로가 포함된 컴포넌트 중 가장 먼저 나온 파우트 페이지를 렌더링한다.

따라서 우리에게는 이에 대한 해결방법이 2가지가 있다.

컴포넌트 순서 바꾸기

// ...
      <Switch>
        <Route path="/welcome">
          <Welcome />
        </Route>
        <Route path="/products/:productId">
          <ProductDetail />
        </Route>
        <Route path="/products">
          <Products />
        </Route>
      </Switch>
// ...

단순히 라우팅 페이지의 순서를 변경시킨다.

이럴 경우 먼저 입력된 <ProductDetail> 페이지가 우선순위를 가져 렌더링된다.

exact 부여하기

// ...
      <Switch>
        <Route path="/welcome">
          <Welcome />
        </Route>
        <Route path="/products" exact>
          <Products />
        </Route>
        <Route path="/products/:productId">
          <ProductDetail />
        </Route>
      </Switch>
// ...

페이지에 exact를 부여하면 해당 페이지는 완전히 일치한 경우에만 렌더링된다.

domain.com/products/p1은 비록 /products 경로는 포함되지만 동적 라우팅으로 인해 추가된 값으로 인해 완전히 일치하지는 않으므로 렌더링 되지 않는다.

useParams로 동적 라우팅 활용하기

동적 라우팅 통해 접속한 페이지에서 Path Parameter를 어떻게 가져올까?
바로 useParams 커스텀훅을 사용하면 Path Parameter에 접근 할 수 있다.

예를 들어서

<Route path='/products/:productId'>
  <ProductDetail />
<Route>

위와 같은 path를 갖는 ProductDetail 동적 라우팅 페이지가 존재한다 가정하자.
그렇다면 동적 라우팅 페이지에 접근하기 위해서 어플리케이션 어딘가에는

<Link to="/products/{productId}">
	// Product Data
</Link>

위 형태의 Link 컴포넌트를 통해 url을 업데이트 할 것이다.

이때 Path Parameter로 사용될 productID1234라고 가정한다면 사용자는 www.domain.com/products/1234에 접속하게 될 것이고 동적 라우팅된 ProductDetail 페이지에 접근하게 될 것이다.

이때 ProductDetail에서 접근할때 사용된 Path Parameter를 읽어오기 위해서 useParams 커스텀 훅을 사용한다.

const ProductDetail = () => {
  const params = useParams();
  
  console.log(params) // { productId: '1234' }

  return (
    <Fragment>
      <p>
    	{params.productId} // = <p>1234</p>
      </p>
    </Fragment>
  );
};

useParams() 커스텀 훅은 객체를 반환하는데 이때 객체는 동적 라우팅 페이지를 접근하면서 사용된 Path Parameter를 가진 객체를 반환한다.

<Route path='/products/:productId'>

라우팅 페이지에서 :로 인하여 productId를 Path Parameter로 사용한다고 지시하였으니 useParams 훅은 다음과 같은 객체를 반환한다.

{ productId: '1234' }

위처럼 Path Parameter의 구분기준인 productId를 key로 갖고 Path Parameter의 값을 value로 갖는 형태의 객체를 반환한다.

이러한 로직에 의하여 ProductDetail 컴포넌트 내부에서 useParams의 반환값인 params에서 params.productId로 접근하여 사용 할 수 있게되는 것이다.

사용자의 리디렉션 유발하기

많은 어플리케이션들은 메인페이지를 담당하는 기본 도메인으로 domain.com 형태를 가지고 있다.

또한 사용자들도 저런 기본적인 형태의 도메인을 익숙하게 여긴다.

하지만 만약 기본 도메인을 통해서 접근했을때 특정 페이지로 리다이렉트 되도록 만들고 싶다면 어떻게 해야할까?

react route에선 이러한 리다이렉션을 위해 Redirect 컴포넌트가 존재한다.

// ...
      <Switch>
        <Route path="/" exact>
          <Redirect to="/welcome" />
        </Route>
        <Route path="/welcome">
          <Welcome />
        </Route>
        <Route path="/products" exact>
          <Products />
        </Route>
        <Route path="/products/:productId">
          <ProductDetail />
        </Route>
      </Switch>
// ...

위와 같이 라우트 페이지의 path/만 입력된 기본값을 부여하고 exact를 할당한다.
(exact 미할당시 어떤 url이건 해당 기본 페이지가 렌더링되는 문제가 발생한다!)

<Redirect> 컴포넌트에 to에 리다이렉트를 원하는 url을 부여하면 해당 url의 페이지 접속시 to에 입력된 url로 리다이렉트 된다.

존재하지 않는 페이지 접근 대처

사용자가 항상 규칙을 지킨 url을 타고 접속하리란 보장은 없다.

브라우저의 url 입력창에 임의적인 url 경로를 입력했을때 어떻게 대처해야할까?

네이버에서 지정되지 않은 경로로 접근시 나오는 페이지

위 사진처럼 대부분의 어플리케이션에선 NOT FOUND 페이지를 만드는 것이 보편화돼있다.

이를 구현하는 법은 Routepath*을 할당하는 것이다.

path*와일드카드 문자로서 그 어떤 값이 온다고 하더라도 해당된다는 의미와 같다. 따라서 라우팅을 담당하는 컴포넌트의 <Switch> 가장 마지막에 NOT FOUND 페이지를 지정하면 브라우저는 적합한 페이지를 찾지 못하고 마지막에 와일드카드 문자가 지정된 NOT FOUND 페이지로 이동하게 된다.

// ...
      <Switch>
        <Route path="/" exact>
          <Redirect to="/welcome" />
        </Route>
        <Route path="/welcome">
          <Welcome />
        </Route>
        <Route path="/products" exact>
          <Products />
        </Route>
        <Route path="/products/:productId">
          <ProductDetail />
        </Route>
        <Route path="*">
          <NotFound />
        </Route>
      </Switch>
// ...

로직에 의한 페이지 이동

지금까지 작성한 코드들은 항상 사용자의 이벤트 발생으로 인해 이동하는 경우만이 존재한다.

하지만 이벤트 발생이 아닌 로직에 의한 페이지 이동을 의도해야 할 때가 있다.

예를 들면 블로그에서 포스트를 업로드하는 경우에도 업로드 이후에 블로그의 메인 페이지로 이동하도록 설계돼있듯이 말이다.

이때 <Link>를 이용하는것만으로는 구조적으로 한계가 존재한다.

이를 위해서 존재하는 커스텀 훅이 바로 useHistory이다.

-react router v6에서는 useNavigation을 사용함-

useHistory는 사용자의 페이지 이동을 개발자가 직접 지정할 수 있도록 해주는 메서드가 모인 객체를 반환한다.

referenced by
라우팅이란 무엇입니까? - AWS
React Router DOCS
React Complete Guide - udemy

profile
Error Driven Development

0개의 댓글