관리 메뉴

거니의 velog

(6) 리액트 라우터로 SPA 개발하기 2 본문

React/React_리액트 응용

(6) 리액트 라우터로 SPA 개발하기 2

Unlimited00 2023. 12. 11. 14:30

3. Route 하나에 여러 개의 path 설정하기

* Route 하나에 여러 개의 path를 지정하는 것은 최신 버전의 리액트 라우터 v5부터 적용된 기능이다. 이전 버전에서는 여러 개의 path에 같은 컴포넌트를 보여 주고 싶다면 다음과 같이 해야 했다.

import React from "react";
import { Link, Route } from "react-router-dom/cjs/react-router-dom";
import Home from "./Home";
import About from "./About";

const App = () => {
  return (
    <div>
      <Route path="/" component={Home} exact={true} />
      <Route path="/about" component={About} />
      <Route path="/info" component={About} />
    </div>
  );
};

export default App;

* 이렇게 Route를 두 번 사용하는 대신, path props를 배열 ["/about", "/info"] 로 설정해 주면 여러 경로에서 같은 컴포넌트를 보여 줄 수 있다. 

import React from "react";
import { Link, Route } from "react-router-dom/cjs/react-router-dom";
import Home from "./Home";
import About from "./About";

const App = () => {
  return (
    <div>
      <ul>
        <li>
          <Link to="/">홈</Link>
        </li>
        <li>
          <Link to="/about">소개1</Link>
        </li>
        <li>
          <Link to="/info">소개2</Link>
        </li>
      </ul>
      <Route path="/" component={Home} exact={true} />
      <Route path={["/about", "/info"]} component={About} />
    </div>
  );
};

export default App;

* React-Router v6 이후는 아래 접은 글을 참조하자.

- react-router-dom 버전 6 부터 <Routes> 태그로 <Route /> 태그를 감싸는 규칙이 추가되어 불가능하다.

더보기

import React from "react";
import { Link, Route, Routes } from "react-router-dom";
import About from "./About";
import Home from "./Home";

const App = () => {
  return (
    <div>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/info" element={<About />} />
      </Routes>
    </div>
  );
};

export default App;


* 코드를 저장하고, 주소창에 다음 주소를 입력해 들어가 보자. 소개 페이지가 잘 나타나는가?

- http://localhost:3000/


4. URL 파라미터와 쿼리

* 페이지 주소를 정의할 때 가끔은 유동적인 값을 전달해야 할 때도 있다. 이는 파라미터와 쿼리로 나눌 수 있다.

- 파라미터 예시 : /profile/bbbb7788
- 쿼리 예시 : /about?details=true

* 유동적인 값을 사용해야 하는 상황에서 파라미터를 써야 할지 쿼리를 써야 할 지 정할 때, 무조건 따라야 하는 규칙은 없다. 다만, 일반적으로 쿼리는 특정 아이디 혹은 이름을 사용하여 조회할 때 사용하고, 쿼리는 우리가 어떤 키워드를 검색하거나 페이지에 필요한 옵션을 전달할 때 사용한다.


(1) URL 파라미터

* Profile 페이지에서 파라미터를 사용해 보자. /profile/bbbb7788과 같은 형식으로 뒷 부분에 유동적인 username 값을 넣어줄 때 해당 값을 props로 받아와서 조회하는 방법을 알아보자.

* Profile이라는 컴포넌트를 다음과 같이 만들어 보자.

import React from "react";

const data = {
  bbbb7788: {
    name: "거니",
    description: "리액트를 공부하는 개발자",
  },
  gildong: {
    name: "길동",
    description: "고전 소설 홍길동전의 주인공",
  },
};

const Profile = ({ match }) => {
  const { username } = match.params;
  const profile = data[username];
  if (!profile) {
    return <div>존재하지 않는 사용자 입니다.</div>;
  }
  return (
    <div>
      <h3>
        {username}({profile.name})
      </h3>
      <p>{profile.description}</p>
    </div>
  );
};

export default Profile;

* URL 파라미터를 사용할 때는 라우트로 사용되는 컴포넌트에서 받아 오는 match라는 객체 안의 params 값을 참조한다. match 객체 안에는 현재 컴포넌트가 어떤 경로 규칙에 의해 보이는지에 대한 정보가 들어 있다.

* 이제 App 컴포넌트에서 Profile 컴포넌트를 위한 라우트를 정의해 보자. 이번에 사용할 path 규칙에는 /profile/:username이라고 넣어 주면 된다. 이렇게 설정하면 match.params.username 값을 통해 현재 username 값을 조회할 수 있다.

* 라우트를 정의하고 나서 상단에 각 프로필 페이지로 이동하는 링크도 추가해 보자.

[App.js]

import React from "react";
import { Link, Route } from "react-router-dom/cjs/react-router-dom";
import Home from "./Home";
import About from "./About";
import Profile from "./Profile";

const App = () => {
  return (
    <div>
      <ul>
        <li>
          <Link to="/">홈</Link>
        </li>
        <li>
          <Link to="/about">소개1</Link>
        </li>
        <li>
          <Link to="/info">소개2</Link>
        </li>
        <li>
          <Link to="/profile/bbbb7788">거니의 프로필</Link>
        </li>
        <li>
          <Link to="/profile/gildong">gildong의 프로필</Link>
        </li>
      </ul>
      <Route path="/" component={Home} exact={true} />
      <Route path={["/about", "/info"]} component={About} />
      <Route path="/profile/:username" component={Profile} />
    </div>
  );
};

export default App;

* react-router-dom 버전 6부터는 element로 컴포넌트를 만들고, route props(match, history, location)을 받지 않는다. 따라서, useParams, useLocation, useHistory를 사용하여 route context에 접근한다.

출처 : https://velog.io/@kcdoggo/Cannot-read-property-params-of-undefined-%EC%97%90%EB%9F%AC

 

Cannot read property 'params' of undefined 에러

react-router-dom에서 Route props으로 match, location, history가 있었고 match객체는 params의 properties를 가지고 있다.그런데, props.match.params.id 로 아이디를 <Route path="/

velog.io

[Profile.js] : 접은 글 참조

더보기

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

const data = {
  bbbb7788: {
    name: "거니",
    description: "리액트를 공부하는 개발자",
  },
  gildong: {
    name: "길동",
    description: "고전 소설 홍길동전의 주인공",
  },
};

const Profile = () => {
  const params = useParams();
  const profile = data[params.username];

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

export default Profile;

[App.js] : 접은 글 참조

더보기

import React from "react";
import { Link, Route, Routes } from "react-router-dom";
import About from "./About";
import Home from "./Home";
import Profile from "./Profile";

const App = () => {
  return (
    <div>
      <ul>
        <li>
          <Link to="/">홈</Link>
        </li>
        <li>
          <Link to="/about">소개1</Link>
        </li>
        <li>
          <Link to="/info">소개2</Link>
        </li>
        <li>
          <Link to="/profile/bbbb7788">거니의 프로필</Link>
        </li>
        <li>
          <Link to="/profile/gildong">gildong의 프로필</Link>
        </li>
      </ul>
      <hr />
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/info" element={<About />} />
        <Route path="/profile/:username" element={<Profile />} />
      </Routes>
    </div>
  );
};

export default App;


- http://localhost:3000/profile/bbbb7788

- http://localhost:3000/profile/gildong


(2) URL 쿼리

* 이번에는 About 페이지에서 쿼리를 받아오자. 쿼리는 locaion 객체에 들어 있는 search 값에서 조회할 수 있다. location 객체는 라우트에 사용된 컴포넌트로 props로 전달되며, 웹 애플리케이션의 현재 주소에 대한 정보를 지니고 있다.

* location의 형태는 다음과 같다.

{
    "pathname" : "/about",
    "search" : "?detail=true",
    "hash" : ""
}

* 위 location 객체는 http://localhost:3000/about?detail=true 주소로 들어갔을 때의 값이다. URL 쿼리를 읽을 때는 위 객체가 지닌 값 중에서 search 값을 확인해야 한다. 이 값은 문자열 형태로 되어 있다. URL 쿼리는 ?detail=true&another=1과 같이 문자열에 여러 가지 값을 설정해 줄 수 있다. search 값에서 특정 값을 읽어 오기 위해서는 이 문자열을 객체 형태로 변환해 주어야 한다.

* 쿼리 문자열을 객체로 변환할 때는 qs라는 라이브러리를 사용한다. yarn을 사용하여 해당 라이브러리를 설치하자.

$ yarn add qs

* 그러면 이제 About 컴포넌트에서 location.search 값에 있는 detail이 true 인지 아닌지에 따라 추가 정보를 보여 주도록 만들어 보겠다. About 컴포넌트를 다음과 같이 수정해 보자.

import React from "react";
import qs from "qs";

const About = ({ location }) => {
  const query = qs.parse(location.search, {
    ignoreQueryPrefix: true, // 이 설정을 통해 문자열 맨 앞의 ?를 생략한다.
  });
  const showDetail = query.detail === "true"; // 쿼리의 파싱 결과 값은 문자열이다.
  return (
    <div>
      <h1>소개</h1>
      <p>
        이 프로젝트는 리액트 라우터 기초를 실습해 보는 예제 프로젝트 입니다.
      </p>
      {showDetail && <p>detail 값을 true로 설정하셨군요!</p>}
    </div>
  );
};

export default About;

* React-Router v6 이후는 아래 접은 글을 참조하자.

더보기

import React from "react";
import qs from "qs";
import { useLocation } from "react-router-dom";

const About = () => {
  const location = useLocation();
  const query = qs.parse(location.search, {
    ignoreQueryPrefix: true, // 이 설정을 통해 문자열 맨 앞의 ?를 생략한다.
  });
  const showDetail = query.detail === "true"; // 쿼리의 파싱 결과 값은 문자열이다.
  return (
    <div>
      <h1>소개</h1>
      <p>
        이 프로젝트는 리액트 라우터 기초를 실습해 보는 예제 프로젝트 입니다.
      </p>
      {showDetail && <p>detail 값을 true로 설정하셨군요!</p>}
    </div>
  );
};

export default About;

* 위 컴포넌트에서 우리는 useLocation 이라는 Hook을 사용했는데, 이 Hook은 location 객체를 반환하며 이 객체는 현재 사용자가 보고있는 페이지의 정보를 지니고 있다. 이 객체에는 다음과 같은 값들이 있다.

- pathname: 현재 주소의 경로 (쿼리스트링 제외)
- search: 맨 앞의 ? 문자 포함한 쿼리스트링 값
- hash: 주소의 # 문자열 뒤의 값 (주로 History API 가 지원되지 않는 구형 브라우저에서 클라이언트 라우팅을 사용할 때 쓰는 해시 라우터에서 사용합니다.)
- state: 페이지로 이동할때 임의로 넣을 수 있는 상태 값
- key: location 객체의 고유 값, 초기에는 default 이며 페이지가 변경될때마다 고유의 값이 생성됨

* 쿼리스트링은 location.search 값을 통해 조회를 할 수 있다.


* 쿼리를 사용할 때는 쿼리 문자열을 객체로 파싱하는 과정에서 결과 값은 언제나 문자열이라는 점에 주의하자. ?value=1 혹은 ?value=true와 같이 숫자나 논리 자료형(boolean)을 사용한다고 해서 해당 값이 우리가 원하는 형태로 변환되는 것이 아니라, "1", "true"와 같이 문자열 형태로 받아진다.

* 그렇기 때문에 숫자를 받아 와야 하면 parseInt 함수를 통해 꼭 숫자로 변환해 주고, 지금처럼 논리 자료형 값을 사용해야 하는 경우에는 정확히 "true" 문자열이랑 일치하는지 비교해 주면 된다.

* 코드를 다 작성했다면, 브라우저에서 아래 주소로 직접 들어간 후 접속해 보자.

- http://localhost:3000/about?detail=true

URL 쿼리 사용하기

* 하단에 "detail 값을 true로 설정하셨군요!" 라는 문구가 잘 보이는가?


5. 서브 라우트

* 서브 라우트는 라우트 내부에 또 라우트를 정의하는 것을 의미한다. 이 작업은 그렇게 복잡하지 않다. 그냥 라우트로 사용되고 있는 컴포넌트 내부에 Route 컴포넌트를 또 사용하면 된다.

* 서브 라우트를 직접 한번 만들어 보자. 기존의 App 컴포넌트에서는 두 종류의 프로필 링크를 보여 주었는데, 이를 잘라내서 프로필 링크를 보여 주는 Profiles 라는 라우트 컴포넌트를 따로 만들고, 그 안에서 Profiles 컴포넌트를 서브 라우트로 사용하도록 코드를 작성해 보겠다.

* 우선 Profiles이라는 컴포넌트를 만들어 주자.

[Profiles.js]

import React from "react";
import Profile from "./Profile";
import { Link, Route } from "react-router-dom/cjs/react-router-dom";

const Profiles = () => {
  return (
    <div>
      <h3>사용자 목록</h3>
      <ul>
        <li>
          <Link to="/profiles/bbbb7788">거니</Link>
        </li>
        <li>
          <Link to="/profiles/gildong">gildong</Link>
        </li>
      </ul>

      <Route
        path="/profiles"
        exact
        render={() => <div>사용자를 선택해 주세요.</div>}
      />
      <Route path="/profiles/:username" component={Profile} />
    </div>
  );
};

export default Profiles;

* 이 코드에서 첫 번쨰 Route 컴포넌트에는 component 대신 render라는 props를 넣어 주었다. 컴포넌트 자체를 전달하는 것이 아니라, 보여 주고 싶은 JSX를 넣어 줄 수 있다. 지금처럼 따로 컴포넌트를 만들기 애매한 상황에 사용해도 되고, 컴포넌트에 props를 별도로 넣어 주고 싶을 때도 사용할 수 있다.

* JSX에서 props를 설정할 때 값을 생략하면 자동으로 true로 설정된다. 예를 들어 현재 Profile 컴포넌트의 첫 번째 Route에서 exact={true} 대신 그냥 exact라고만 적어 주었는데, 이는 exact={true} 와 같은 의미이다.


* 하단의 접은 글 코드는 react router v6 기준의 서브 라우팅 구조이므로 참고 바란다.

더보기

import React from "react";
import { Link, Route, Routes } from "react-router-dom";
import Profile from "./Profile";

const Profiles = () => {
  return (
    <div>
      <h3>사용자 목록</h3>
      <ul>
        <li>
          <Link to="/profiles/bbbb7788">거니</Link>
        </li>
        <li>
          <Link to="/profiles/gildong">gildong</Link>
        </li>
      </ul>

      <Routes>
        <Route path="/*" element={<div>유저를 선택해 주세요.</div>} />
        <Route path=":username" element={<Profile />} />
      </Routes>
    </div>
  );
};

export default Profiles;


* 컴포넌트를 다 만들었다면 기존의 App 컴포넌트에 있던 프로필 링크를 지우고, Profiles 컴포넌트를 /profiles 경로에 연결시켜 주자. 그리고 해당 경로로 이동하는 링크도 추가하자.

[App.js]

import React from "react";
import { Link, Route } from "react-router-dom/cjs/react-router-dom";
import Home from "./Home";
import About from "./About";
import Profiles from "./Profiles";

const App = () => {
  return (
    <div>
      <ul>
        <li>
          <Link to="/">홈</Link>
        </li>
        <li>
          <Link to="/about">소개1</Link>
        </li>
        <li>
          <Link to="/info">소개2</Link>
        </li>
        <li>
          <Link to="/profiles">프로필</Link>
        </li>
      </ul>
      <Route path="/" component={Home} exact={true} />
      <Route path={["/about", "/info"]} component={About} />
      <Route path="/profiles" component={Profiles} />
    </div>
  );
};

export default App;

* React-Router v6 이후는 아래 접은 글을 참조하자.

더보기

import React from "react";
import { Link, Route, Routes } from "react-router-dom";
import About from "./About";
import Home from "./Home";
import Profiles from "./Profiles";

const App = () => {
  return (
    <div>
      <ul>
        <li>
          <Link to="/">홈</Link>
        </li>
        <li>
          <Link to="/about">소개</Link>
        </li>
        <li>
          <Link to="/profiles">프로필</Link>
        </li>
      </ul>
      <hr />
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/info" element={<About />} />
        {/* 서브 라우팅 */}
        <Route path="/profiles/*" element={<Profiles />} />
      </Routes>
    </div>
  );
};

export default App;


- http://localhost:3000/profiles

* 서브 라우트가 잘 나타난 모습을 볼 수 있다.