React-Router-Dom 개념잡기

오형근·2023년 5월 8일
55

Frontend

목록 보기
5/10
post-thumbnail

Routing??

라우팅은 기본적으로 네트워크에서 경로를 선택하는 프로세스를 의미한다.

조금 풀어서 얘기하면, 다양한 주소의 요청이 들어왔을 때 각각 맞는 콘텐츠로 이동시켜 주는 작업이라고 볼 수 있다. 마치 우체국에서 편지를 집 주소에 맞게 배달하는 것처럼!!

Routing출처: 생활코딩 블로그


React-Router를 사용하는 이유

그냥 a 태그가 있으니 요거로 이동해도 무방한데
굳이 라우터를 따로 정의하고 사용하는 이유가 있나?

-> SPA 사용자 경험 향상의 목적!!

그냥 a 태그를 사용하면 페이지 전체가 새로 로딩된다.
흔히 말하는 화면 깜빡임이 필수적으로 발생하고 이는 사용자 경험을 떨어뜨리는 큰 요인이다..
따라서 라우팅을 통해 부드러운 화면전환을 꾀하는 것!!!

그러면 그냥 SPA 안에서 모든 페이지를 다 렌더링해주는 형식으로 만들면 안되는건가?

이렇게 하면 아래와 같은 다양한 문제가 발생한다...

  1. 특정 페이지 즐겨찾기 등록 불가 -> 화면 전환이 되어도 url 은 고정되어 있기 때문에... 내가 원하는 페이지를 특정할 수 없다.

  2. 뒤로가기 불가 -> 마찬가지의 이유. 해당 SPA 하나에 url 하나이기 때문에 뒤로 가기를 누르면 이전에 보던 다른 웹사이트로 이동하게 된다.

  3. 새로고침 불가 -> 이 또한 마찬가지! 새로고침을 누를 시 맨 처음의 렌더링 페이지로 이동하게 된다...내가 보던 페이지가 아닌 처음의 페이지가 나온다!

  4. SEO -> 검색 엔진에 의해 원치 않는 방식으로 색인될 가능성이 있다...


Router 종류


HashRouter

URL의 해쉬(#)값을 이용하는 라우터

다음과 같은 특징을 가진다.

  1. 주소에 해쉬(#)가 붙는다.

  2. 검색 엔진이 읽지 못한다...

  3. 별도의 서버 설정을 하지 않더라도 새로고침 시 오류가 발생하지 않는다. 이는 해시 라우터가 해쉬(#) 뒤의 값은 브라우저에서만 관리하고(라우팅하는 사실을 서버가 모름) 서버는 기본 url로 서버에 데이터를 요청하기 때문이다.

  4. history API를 사용하지 않기 때문에 동적 페이지에 불리하다.


BrowserRouter

  1. history API를 사용한다.

  2. 별도의 서버 설정이 없다면 새로고침 시 404에러가 발생한다.

    서버에서는 기본 라우트 '/'정보만 저장되어 있고, 이외의 모든 하위 라우팅은 이 default 경로를 통해 이루어지기 때문에 이 경로를 제외하고는 서버에서 인식하지 못한다. 만일 필요하다면 해당 주소와 그에 맞는 페이지를 서버에 알려주어야 한다. 그런게 아니라면 일반적으로는 '/*'의 경로로 접근 시 서버에는 '/'로 리다이렉션 해주면 된다.
    (404 Error -> index.html)
    배포할 때 많이 에러가 나는 부분이므로 제대로 알고 있도록 하자!!
    참고 자료

  3. 큰 프로젝트에 적합하다.

  4. 우리가 사용할 라우터도 이 BrowserRouter이다!!

서버가 존재하고, SEO가 필요한 프로젝트라면 BrowserRouter를 사용하는 것이 좋고, 그 외에 개인적이거나 작은 단위의 프로젝트라면 HashRouter를 사용해도 괜찮다!!


React-Router-Dom

React로 생성된 SPA 내부에서 페이지 이동이 가능하도록 만들어주는 라이브러리!!


설치하기

yarn add react-router-dom
//ts yarn add @types/react-router-dom

최근 React-Router-Dom v6가 나오면서 관련 문법이 상당수 변경되었다!

따라서 package.json에서 버전이 v6~인지 꼭 확인하고 넘어가자.


세팅하기

import { BrowserRouter, Routes, Route } from 'react-router-dom'

가장 많이 사용되는 모듈 3가지이다!!

BrowserRouter

  • history API를 활용해 history 객체를 생성한다.
  • history API는 내부적으로 stack 자료구조의 형태를 띄기 때문에 사용자가 방문한 url 기록들을 차곡차곡 쌓는 형태로 저장해둔다고 생각하면 된다.
  • 라우팅을 진행할 컴포넌트 상위에 BrowserRouter 컴포넌트를 생성하고 감싸주어야 한다!!

Route

  • 현재 브라우저의 location(window.href.location 정보를 가져온다) 상태에 따라 다른 element를 렌더링한다.
  • Route.element: 조건이 맞을 때 렌더링할 element,
    <Element />의 형식으로 전달된다!!
  • Route.path: 현재 path값이 url과 일치하는지 확인해 해당 url에 매칭된 element를 렌더링해준다!!

Routes

  • 모든 Route의 상위 경로에 존재해야 하며, location 변경 시 하위에 있는 모든 Route를 조회해 현재 location과 맞는 Route를 찾아준다!

아래와 같은 방식으로 기본적인 설정을 한다.

const Router = () => {
  return (
    <BrowserRouter>
        <Routes>
          <Route path="/" element={<GalleryPage />} />
          <Route path="/gallery" element={<DetailCardPage />}>
            <Route path=":cardId" element={<DetailCard />} />
          </Route>
        </Routes>
    </BrowserRouter>
  );
};

위와 같은 방식으로 Router 컴포넌트를 생성해준 뒤,

import Router from "./Router";

const App = () => (
  <>
      <Router />
  </>
);

export default App;

상위 렌더링 요소에 컴포넌트를 붙여준다!


Link 컴포넌트는 라우터 내에서 직접적으로 페이지 이동을 하고자 할 때 사용되는 컴포넌트이다.

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

function Nav(){
  return (
    <div>
      <Link to='/'> Home </Link>
      <Link to='/about'> About </Link>
    </div>
  );
}

export default Nav;

위와 같은 방식으로 간단하게 to 속성에 경로를 넣어주는 방식으로 사용한다!
Link 컴포넌트는 다음과 같은 특징을 갖는다.

1. Relative

  • 계층 구조에 상대적이다.
  • 상대 경로 표현이 가능하므로, ./.. 과 같은 표현도 사용이 가능하다.

2. preventScrollReset

  • 페이지 중간에 있는 컨텐츠 내부에서 tab 목록을 누르는 것과 같은 시도를 할 때, 기존의 Link 컴포넌트였다면 클릭 시 스크롤이 초기화되어 페이지 가장 위로 이동하게 된다.
  • 그러나 이 속성을 true로 설정해주면 이를 방지할 수 있다!!

3. state

  • useLocation 훅과 연계하여 특정 state를 넘겨주는 것도 가능하다.
<Link to="new-path" state={{ some: "value" }} />

let { state } = useLocation();

Link는 a태그로 이루어져 있지만, 자체적으로 컴포넌트의 상태를 유지하거나, 화면 전체 리렌더링을 방지하는 등의 기능이 포함된 a 태그의 상위 버전이라고 생각하면 좋을 것 같다!!


useNavigate

그럼 내가 일반 article 요소를 클릭했을 때 이동을 하고 싶으면 어쩌지..? Link로 무작정 감싸야 하나..?

이럴 때 사용되는 게 바로 useNavigate 훅이다.

useNavigate훅을 사용하면 특정 이벤트(onChange, onClick 등)가 발생했을 때 페이지 이동을 트리거할 수 있다.

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

const navigate = useNavigate();

const onClick = () => {
	navigate('/')
}

위와 같이 사용한다!!
useNavigate에는 다음과 같은 속성을 추가할 수 있다.

  1. replace
    • 기본값은 false이고, true로 설정한다면 이동 후 뒤로가기가 불가능해진다.
navigate("/", { replace: true });
  1. state
    • Link와 마찬가지로 state를 전달해줄 수 있다!!!
navigate("/", { state: { cardId: cardId } });

const location = useLocation();
const { cardId } = location.state;

중첩 라우팅

개인적으로 React-router-dom 내장 기능 중 가장 유용하게 사용하고 있는 기능이다. 특정 페이지 내에서 하위 페이지를 만들 수 있고, 해당 페이지마다 경로를 이용한 데이터 전달도 가능하다!

또한 중첩 라우팅을 구현할 경우 해당 하위 페이지 이외에는 컨텐츠가 바뀌지 않는다!

<Route path="/about" element={<About />}>
  <Route path="location" element={<Location />}></Route>
</Route>

라우터 내부에 위와 같이 자식 요소 Route를 만들어준다. 이렇게만 설정해도 라우터 내부적으로는 /about 주소 하위에 /location 이라는 하위 라우팅이 되었다고 판단한다. 따라서 우리가 /about/location으로 주소를 이동할 경우, 주어진 Location 컴포넌트가 렌더링되는 것이다(물론 About컴포넌트도 같이 렌더링된다.)!!

물론 라우터에서 위와 같이 설정한 것 만으로는 아무런 변화가 생기지 않는다.

실제로 해당 라우팅이 발생하는 부모 요소인 About 페이지에 하위 라우팅 발생 시 컴포넌트를 렌더링할 자리를 명시해주어야하기 때문이고, 이때 사용되는 것이 Outlet 컴포넌트이다.


Outlet

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

function About() {
  return (
    <div>
      <div>
        <h2>여기는 About 페이지입니다.</h2>
        <p>대충 쇼핑몰 페이지라는 뜻</p>
      </div>
      <Outlet />
    </div>
  );
}

이처럼 About 컴포넌트 내부에 Outlet 컴포넌트를 렌더링해주면 라우터에서 이를 인식하고 Outlet 자리에 Location 컴포넌트를 렌더링하게 되는 것이다.(물론 주소가 일치하는 경우)


Outlet 없이 중첩 라우팅 구현하기

Outlet 없이도 중첩 라우팅을 구현할 수 있다.

우선 라우터에서 중첩 라우팅을 하고자 하는 주소에 다음과 같이 * 을 추가해주어 중첩 라우팅이 발생할 주소임을 명시해준다.

<Routes>
  <Route path="/" element={<Home />}></Route>
  <Route path="/about/*" element={<About />}></Route>
  <Route path="/products" element={<Products />}></Route>
</Routes>

이후 해당 About 컴포넌트에서 본래 Outlet이 들어갔던 자리에 마치 라우터를 구현했던 것처럼 중첩 라우팅을 진행해주면 된다.

function About() {
  return (
    <div>
      <div>
        <h2>여기는 About 페이지입니다.</h2>
        <p>대충 쇼핑몰 페이지라는 뜻</p>
      </div>
      <Routes>
        <Route path="/location" element={<Location />}></Route>
      </Routes>
    </div>
  );
}

위와 같이 작성하면 Outlet 과 동일한 기능을 하는 중첩 라우팅을 구현할 수 있다.


Params

주소 경로 내부에 특정 데이터를 넣어 전달하는 것을 말하는데, 크게 url 파라미터와 쿼리스트링으로 나누어진다.

url 파라미터

주소: http://hello.com/new/1234

<Route path="/new/:id" element={<NewId />} />

여기에서 :으로 구분해준 id라는 값은 파라미터로 전달되어 이후 우리가 NewId 컴포넌트 내부에서 useParams훅을 통해 추출하고 사용할 수 있다. 이때 전달된 값은 1234가 된다!!

쿼리스트링

?, &을 기준으로 key와 value를 나눠 데이터를 전달받는다. 이렇게 전달 받은 값은 이후 useLocation훅을 통해 추출하고 사용할 수 있다.

이전까지는 ?, &을 직접 분리해 추출해야하는 번거로움이 있었는데, useSearchParams를 사용하면 쉽게 해결할 수 있다!!


url 파라미터 VS 쿼리스트링

url 파라미터쿼리 스트링
ID, 이름, 특정 데이터를 조회할때 사용키워드 검색, 페이지네이션, 정렬방식 등 데이터 조회에 필요한 옵션을 전달할 때 사용
일반적인 변수, 상수 값들을 전달하기 용이key, value 형태의 데이터이므로 json이나 객체 형태의 데이터를 전달하기 용이

useParams

위에서 말한 것처럼 url 파라미터를 조회할 때 사용한다.

실제 사용 예시를 살펴보자!!

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

const NewId = () => {
  let { id } = useParams();

  return (
    <div className="test">
      <p>현재 유저의 아이디는 { id } 입니다.</p>
    </div>
  )
}

export default NewId;

이처럼 http://hello.com/new/1234의 주소로 이동하여 렌더링된 NewId 컴포넌트 내부에서 useParams를 이용해 아이디 값을 받아올 수 있다!!

http://hello.com/new/1234/1212 와 같은 주소에 라우팅을 /new/:id/:lastId 와 같은 방식으로 해준다면 여러 개의 값도 한 번에 전달받을 수 있다!!


useSearchParams

위에서 언급한 것처럼 쿼리스트링을 추출하는 데 사용된다. 현재 위치에 대한 url의 쿼리스트링을 읽고 수정할 때 사용한다!!

useState와 사용법이 유사하므로 처음 적용하기 어렵지 않다.

const [serchParams, setSearchParams] = useSearchParams();

searchParams는 URLSearchParams 객체이면서 쿼리스트링을 다루기 위한 다양한 메서드를 내장하고 있다.

setSearchParams는 함수의 인자에 객체와 문자열을 넣어주면 현재 url의 쿼리스트링을 변경하는 기능을 제공한다!!

자주 사용하는 메서드를 살펴보자.

값을 읽어오는 메서드

searchParams.get(key) : 특정한 key의 value를 가져오는 메서드, 해당 key의 value가 두 개 이상이라면 처음의 값을 반환한다.
searchParams.getAll(key) : 특정 key에 해당하는 모든 value를 가져오는 메서드

값을 변경하는 메서드

searchParams.set(key, value) : 인자로 전달한 key 값을 value로 설정한다. 만일 기존에 key에 대한 값이 존재했다면 덮어씌운다.
searchParams.append(key, value) : 기존 값을 변경 혹은 삭제하지 않고 추가한다.

searchParams 를 변경하는 메서드로 값을 변경하더라도 실제 url 에는 이 정보가 반영되지 않는다. 만일 이를 반영하고자 한다면 setSearchParams에 searchParams를 인자로 전달해주어야 한다!

쿼리스트링은 페이지네이션이나 키워드 검색, 정렬 등 꽤 다양한 곳에서 용이하게 사용되므로 위에서 언급한 기본적인 내용들과 대표 메서드들을 꼭 숙달하도록 노력하자!!!


Reference

공식문서
리액트 기초, 쿼리 스트링, useSearchParams
+GO SOPT 세미나 자료

profile
eng) https://medium.com/@a01091634257

3개의 댓글

comment-user-thumbnail
2024년 9월 26일

혹시 /new/:id/lastId에서 lastId에는 : 이 없는데 불러와 질수가 있나요?

1개의 답글