[React][리액트를 다루는 기술] 리액트 라우터와 SPA

uddi·2024년 9월 23일
0

React

목록 보기
17/19

📌 라우팅이란?

사용자가 요청한 URL에 따라 알맞은 페이지를 보여주는 것을 말한다

여러 페이지로 구성된 웹 애플리케이션을 만들 때 페이지 별로 컴포넌트들을 분리해가며 프로젝트를 관리하기 위해 필요한 것이 라우팅 시스템이다

🍞 리액트에서 라우트 시스템 구축을 위한 선택지

  • 리액트 라우터
    리액트의 라우팅 관련 라이브러리들 중 가장 오래됐고, 가장 많이 사용되는 라이브러리
    컴포넌트 기반으로 라우팅 시스템을 설정할 수 있다

  • Next.js
    리액트 프로젝트의 프레임워크
    리액트 프로젝트 설정을 하는 기능, 라우팅 시스템, 최적화, 다국어 시스템 지원, 서버 사이드 렌더링 등 다양한 기능을 제공한다
    라우팅 시스템은 파일 경로 기반으로 작동하며 리액트 라우터의 대안으로 많이 사용되고 있다

📌 싱글 페이지 애플리케이션(SPA)

SPA란 하나의 페이지로 이루어진 애플리케이션을 뜻한다

🍞 멀티 페이지 애플리케이션

멀티 페이지 애플리케이션에서는 사용자가 다른 페이지로 이동할 때마다 새로운 html을 받아오고, 페이지를 로딩할 때마다 서버에서 CSS, JS, 이미지 파일 등의 리소스를 전달받아 브라우저 화면에 보여주었다

즉, 각 페이지마다 다른 html 파일이 존재했다

사용자 인터랙션이 많고 다양한 정보를 제공하는 모던 웹 애플리케이션은 이 방법이 적합하지 않다
👉 새로운 페이지를 보여줄 때마다 서버 측에서 제공하기 때문에 그만큼 서버의 자원을 사용하는 것이고, 트래픽도 더 많이 나오기 때문

그래서 리액트 같은 라이브러리를 사용해 뷰 렌더링을 사용자의 브라우저가 담당하도록 하고, 웹 애플리케이션을 브라우저에 불러와 실행시킨 후 사용자와의 인터랙션이 발생하면 필요한 부분만 JS를 사용해 업데이트하는 방식을 사용하게 됐다
새로운 데이터가 필요하면 서버 API를 호출해 필요한 데이터만 불러오면 된다

싱글 페이지 애플리케이션 : html은 한 번만 받아와서 웹 애플리케이션을 실행시킨 후, 이후에는 필요한 데이터만 받아와 화면에 업데이트하는 것

📌 리액트 라우터

🍞 리액트 라우터 동작원리

리액트 라우터 같은 라우팅 시스템은 사용자의 브라우저 주소창 경로에 따라 알맞은 페이지를 보여준다

이후 링크를 눌러 다른 페이지로 이동할 때 브라우저의 History API를 사용해 브라우저의 주소창 값만 변경하고 기존에 페이지에 띄웠던 웹 애플리케이션을 그대로 유지하며 라우팅 설정에 따라 또 다른 페이지를 보여준다

🍞 리액트 프로젝트에 라우터 적용하기

src/index.js 파일에서 BrowserRouter라는 컴포넌트를 사용해 감싸면 된다

BrowserRouter : 웹 애플리케이션에 History API를 사용해 페이지를 새로 불러오지 않고도 주소를 변경하고 현재 주소 경로에 관련된 정보를 리액트 컴포넌트에서 사용할 수 있도록 해 준다

import { BrowserRouter } from "react-router-dom";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>
);

🍞 Route 컴포넌트 사용법

사용자의 브라우저 주소 경로에 따라 원하는 컴포넌트를 보여주기 위해 Route라는 컴포넌트를 통해 라우트 설정을 해줘야 한다
<Route path="주소규칙" element={보여 줄 컴포넌트 JSX} />

Route 컴포넌트는 Routes 컴포넌트 내부에서 사용되어야 한다

📌 URL 파라미터와 쿼리스트링

  • URL 파라미터 예시 : /profile/velopert
  • 쿼리스트링 예시 : /articles?page-1&keyword=react

URL 파라미터는 주소의 경로에 유동적인 값을 넣는 형태, 쿼리스트링은 주소의 뒷부분에 ? 문자열 이후 key=value로 값을 정의하며 &로 구분하는 형태다

URL 파라미터는 주로 ID 혹은 이름을 사용해 특정 데이터를 조회할 때 사용하고, 쿼리스트링은 키워드 검색, 페이지네이션, 정렬 방식 등 데이터 조회에 필요한 옵션을 전달할 때 사용한다

🍞 URL 파라미터

const Profile = () => {
  const params = useParams();
  const profile = data[params.username];	// URL 파라미터의 이름이 username이라서 이렇게 표현 

  return (
    <div>
      <h1>사용자 프로필</h1>
      {profile ? (
        <div>
          <h2>{profile.name}</h2>
          <p>{profile.description}</p>
        </div>
      ) : (
        <p>존재하지 않는 프로필</p>
      )}
    </div>
  );
};

URL 파라미터는 useParams라는 Hook을 사용해 객체 형태로 조회할 수 있다

<Route path="/profiles/:username" element={<Profile />} />
:username처럼 path props를 통해 URL 파라미터의 이름을 설정할 수 있다

username이 아닌 다른 이름으로 해도 무관, : 뒤에 적어야 함

URL 파라미터가 여러 개인 경우에는 /profiles/:username/:field와 같은 형태로 설정할 수 있다

🍞 쿼리스트링

쿼리스트링은 URL 파라미터와 달리 Route 컴포넌트를 사용할 때 별도로 설정해야 하는 것이 없다

✏️ useLocation

이 Hook은 location 객체를 반환하며 다음과 같은 값들을 가진다

  • pathname: 현재 주소의 경로 (쿼리스트링 제외)
  • search: 맨 앞의 ? 문자를 포함한 쿼리스트링 값
  • hash: 주소희 # 문자열 뒤의 값
  • state: 페이지로 이동할 때 임의로 넣을 수 있는 상태 값
  • key: location 객체의 고유값, 초기에는 default이며 페이지가 변경될 때마다 고유값이 생성됨

주소창에 your주소?detail=true&mode=1이라고 입력 후 location.search를 해보면 ?detail=true&mode=1을 나타낸다

?를 지우고 &로 분리해 key와 value를 파싱하는 작업은 리액트 라우터 v6부터 useSearchParams Hook을 통해 처리할 수 있다

✏️ useSearchParams

const About = () => {
  const [searchParams, setSearchParams] = useSearchParams();
  const detail = searchParams.get("detail");
  const mode = searchParams.get("mode");

  const onToggleDetail = () => {
    setSearchParams({ mode, detail: detail === "true" ? false : true });
  };

  const onIncreaseMode = () => {
    const nextMode = mode === null ? 1 : parseInt(mode) + 1;
    setSearchParams({ mode: nextMode, detail });
  };

  return (
    <div>
      <h1>소개</h1>
      <p>리액트 라우터를 사용해 보는 프로젝트</p>
      <p>detail: {detail}</p>
      <p>mode: {mode}</p>
      <button onClick={onToggleDetail}>Toggle detail</button>
      <button onClick={onIncreaseMode}>mode+1</button>
    </div>
  );
};
export default About;

useSearchParams는 배열 타입의 값을 반환하며, 첫 번째 원소는 쿼리파라미터를 조회하거나 수정하는 메서드들이 담긴 객체를 반환한다 (useState와 비슷)

get 메서드로 특정 쿼리파라미터를 조회할 수 있고, set 메서드로 업데이트할 수 있다

조회 시 쿼리파라미터가 존재하지 않는다면 null로 조회된다

두 번쨰 원소는 쿼리파라미터를 객체 형태로 업데이트할 수 있는 함수를 반환한다

📢 쿼리파라미터 사용할 때 주의점
쿼리파라미터를 조회할 때 값은 무조건 문자열 타입

ex) true나 false 값을 넣는다면 값을 비교할 때 꼭 'true' 처럼 따옴표로 감싸야 하고 숫자를 다룰 때는 parseInt를 사용해 숫자 타입으로 변환해야 한다

📌 중첩된 라우트

<Route path="/articles" element={<Articles />} />
<Route path=":id" element={<Article />} />

보통은 이렇게 라우트 설정을 하는데, 이렇게 하면 게시글 목록 페이지에서 게시글을 열었을 때 하단에 목록을 보여주고 싶은 경우

<div>
  <h2>게시글 {id}</h2>
  <ArticleList />
</div>

이렇게 컴포넌트를 따로 만들어 사용해야한다

중첩된 라우트를 사용하면 Outlet 컴포넌트를 사용해 좀 더 간결하게 구현할 수 있다

<Route path="/articles" element={<Articles />}>
  <Route path=":id" element={<Article />} />
</Route>

Outlet은 Route의 children으로 들어가는 JSX 엘리먼트를 보여주는 역할을 한다

const Articles = () => {
  return (
    <div>
      <Outlet />
      <ul>
        <li>
          <Link to="/articles/1">게시글 1</Link>
        </li>
        <li>
          <Link to="/articles/2">게시글 2</Link>
        </li>
        <li>
          <Link to="/articles/3">게시글 3</Link>
        </li>
      </ul>
    </div>
  );
};

export default Articles;

Outlet 컴포넌트가 사용된 자리에 중첩된 라우트(여기서는 Article 컴포넌트)가 보여지게 된다

🍞 공통 레이아웃 컴포넌트

중첩된 라우트와 Outlet은 공통적으로 보여줘야 하는 레이아웃 (header, nav, sidebar 등)이 있을 때도 유용하다

Header 컴포넌트를 따로 만들어두고 각 페이지 컴포넌트에서 재사용해도 되지만 중첩된 라우트를 사용하면 컴포넌트를 한번만 사용해도 된다는 장점이 있다

🍞 index props

Route 컴포넌트에는 index props가 존재하는데, 이 props는 path="/"와 동일한 의미를 가진다

 <Route path="/" element={<Layout />}>
    <Route index element={<Home />} />
    <Route path="/about" element={<About />} />
    <Route path="/profiles/:username" element={<Profile />} />
 </Route>

위 예제처럼 index를 사용하면 상위 경로에 /about과 같은 추가 경로가 붙지 않은 기본 상태에서 보여줄 라우트를 설정할 수 있다

📌 리액트 라우터 부가 기능

🍞 useNavigate

Link 컴포넌트를 사용하지 않고 다른 페이지로 이동해야 하는 상황에 사용하는 Hook

navigate 함수의 파라미터가 숫자 타입이면 앞으로 가거나, 뒤로 간다
ex) navigate(-1) : 뒤로 한 번, navigate(-2) : 뒤로 두 번, navigate(1) : 앞으로 한 번

다른 페이지로 이동할 때 replace 옵션을 사용하면 현재 페이지를 기록에 남기지 않는다

const goArticles = () => {
  navigate('/articles', { replace: true });
}

링크에서 사용하는 경로가 현재 라우트의 경로와 일치하는 경우 특정 스타일 또는 CSS 클래스를 적용하는 컴포넌트

const Articles = () => {
  return (
    <div>
      <Outlet />
      <ul>
        <ArticleItem id={1} />
        <ArticleItem id={2} />
        <ArticleItem id={3} />
      </ul>
    </div>
  );
};

const ArticleItem = ({ id }) => {
  const activeStyle = {
    color: "green",
    fontSize: 21,
  };
  return (
    <li>
      <NavLink
        to={`/articles/${id}`}
        style={({ isActive }) => (isActive ? activeStyle : undefined)}
      >
        게시글 {id}
      </NavLink>
    </li>
  );
};

export default Articles;

🍞 NotFound 페이지 만들기

사전에 정의되지 않는 경로에 사용자가 진입했을 때 보여주는 페이지

<Route path="*" element={<NotFound />} />

*는 wildcart 문자로 아무 텍스트나 매칭한다는 뜻이다
해당 라우트 상단에 위치하는 라우트 규칙을 모두 확인 후 일치하는 라우트가 없다면 실행된다

🍞 Navigate 컴포넌트

컴포넌트를 화면에 보여주는 순간 리다이렉트하고 싶을 때 사용하는 컴포넌트

const Mypage = () => {
  const isLoggedIn = false;

  if (!isLoggedIn) return <Navigate to="/login" replace={true} />;

  return <div>마이 페이지</div>;
};

export default Mypage;

브라우저 주소창에 /mypage 경로를 직접 입력해서 들어가면 페이지가 로딩되는 순간 Login 페
이지로 이동된다

profile
거북이는 느리지만 결국 결승선을 통과한다

0개의 댓글