React(생활코딩)_15일차_React Router_Nested Routing

Lina Hongbi Ko·2023년 10월 3일
0

React_생활코딩

목록 보기
16/23

저번 시간에는 리액트 라우터에 대한 전반적인 개념, 몇몇 컴포넌트와 useNavigate() 훅에 대해서 알아보았다.

오늘은 중첩 라우팅(Nested Routing)에 대해 알아보고, 데이터를 전달하는 방식에 대해 기록해보려 한다.

Nested Routing

✏️ 생코님책 실습하기

이전 게시글에 이어서, 생코님 책의 중첩 라우팅 예제를 실습해보자.

// 실습할 Topics.js 파일

... 생략 ...

function Topics() {
	return(
    	<div>
          <h2>Topics</h2>
          <ul>
              <li><NavLink to="/Topics/1">HTML</NavLink></li>
              <li><NavLink to="/Topics/2">CSS</NavLink></li>
              <li><NavLink to="/Topics/3">React</NavLink></li>
          </ul>
          <Routes>
              <Route path="/Topics/1" element={<Topic />}</Route>
              <Route path="/Topics/2" element={<Topic />}></Route>
              <Route path="/Topics/3" element={<Topic />}></Route>
          </Routes>
        </div>
    );
}

export default Topics;

Topics 컴포넌트 안에 또 하위메뉴를 구성했다.


이 코드를 책에 나온 내용과 v6이후 바뀐 것들을 참고해서 아래처럼 작성했다.

// App.js 파일

... 생략 ...

function App() {
  return (
       ... 생략 ...
      <Routes>
        <Route path="/" element={<Home />} exact></Route>
        <Route path="/topics/*" element={<Topics />}></Route>
        <Route path="/contact" element={<Contact />}></Route>
      </Routes>
    </div>
    ... 생략 ...

먼저, Topics를 가르키는 Route(상위 컴포넌트)의 path에 하위 컴포넌트가 올 것이란 것을 알려주기 위해 path를 위처럼 작성해준다.

// Topics.js 파일

import React from "react";
import { Routes, Route, NavLink } from "react-router-dom";
import { useParams } from "react-router";

const contents = [
  { id: 1, title: "HTML", description: "HTML is ..." },
  { id: 2, title: "JS", description: "JS is ..." },
  { id: 3, title: "React", description: "React is ..." },
];

function Topic() {
  const params = useParams();
  const topic_id = params.topic_id;
  let selected_topic = {
    title: "sorry",
    description: "not found",
  };

  for (let i = 0; i < contents.length; i++) {
    if (contents[i].id === Number(topic_id)) {
      selected_topic = contents[i];
      break;
    }
  }

  console.log(params);
  console.log(topic_id);

  return (
    <div>
      <h3>{selected_topic.title}</h3>
      {selected_topic.description}
    </div>
  );
}

function Topics() {
  let list = [];
  for (let i = 0; i < contents.length; i++) {
    list.push(
      <li key={contents[i].id}>
        <NavLink to={"/topics/" + contents[i].id}>{contents[i].title}</NavLink>
      </li>
    );
  }

  return (
    <div>
      <ul>{list}</ul>
      <Routes>
        <Route path="/:topic_id" element={<Topic />}></Route>
      </Routes>
    </div>
  );
}

export default Topics;

처음 Topics.js파일을 수정하였다.

contents 라는 배열을 가지고, 반복문을 돌려 NavLink 컴포넌트를 추가해서 리스트를 만들었다. 그리고 Route 컴포넌트 3개를 1줄의 코드로 줄여 작성했다. 그리고 Topic 컴포넌트도 추가해주었다.

💡 책에서는 index.js파일에 App 컴포넌트, Topics 컴포넌트, Topic 컴포넌트 모두를 한꺼번에 써서 실습하지만 실제로 작업할때는 파일로 나눠서 관리하며 import할 것이니 나는 따로 파일을 만들었다.

Topic 컴포넌트 안에서는 파라미터를 이용할 것이기 때문에 useParams() 훅을 사용했다.

📍 리액트 라우터에서 콜론(:)과 useParams hook을 이용해 URL 파라미터를 받을 수 있다.

훅을 사용한 후 데이터를 변수에 담았고, 콘솔에 찍어보았다.

params라는 변수에는 객체가 들어와있고, topic_id 속성에 문자열이 들어있는 것을 확인할 수 있다.

다시 한 번 정리하면, useParmas()를 사용해서 파라미터 객체를 받았다. 그리고 ":topic_id"라는 키를 받아왔고, 이를 다시 변수에 넣어주어 해당 파라미터의 값을 가져왔다.
이 값을 이용해서 조건문을 통해 원하는 데이터가 나오도록 하였다.

Topics를 누르면 리스트가 죽 나오고, 그 중 React를 누르면 해당 내용이 나오는 것을 확인할 수 있다.

여기까지 생코님의 책내용을 바탕으로, v6 이후 바뀐 내용들을 찾아 참고해서 실습해보았다.


자, 그래서 일단 실습을 해서 파라미터라는 것을 이용해 우리는 중첩해서 라우팅하는 것을 알게 되었다.

이 실습을 하면서 나는 '파라미터'라는 개념을 잘 알지 못했다. 그래서 다른 사람들이 정리해놓은 블로그와 또 여러 자료들을 찾아보면서 '쿼리스트링'이라는 것도 알게 되었다.

그래서 중첩 라우팅을 하는 여러 방법들을 다시 한번 정리하려고 한다.

✏️ 중첩 라우팅

: 중첩 라우팅이란 서브 페이지를 만드는 것과 같다. (페이지 이동은 하지 않지만^^)
해당 페이지에서 더 구체적으로 구분을 지어 화면을 나타내 줄 필요가 있을 때 사용한다.

예를 들어, 쇼핑몰 상세 페이지에서 myshop/products 라는 주소로 입력했을 때, 쇼핑몰에 게시된 상품들이 나열되어 있다고 해보자.

그리고 여러 상품들 중 하나의 상품을 클릭하면 해당하는 상품의 상세 페이지를 보여주고 싶을 것이다. 이럴 때 중첩 라우팅을 사용할 수 있다.

myshop/products/01과 같은 URL을 입력했다면 쇼핑몰 상품의 01번째 아이템 페이지가 나타나야 한다.

// App.js파일

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

function App() {
  return (
    <div className="App">
      <div>
        <button><Link to="/"> HOME </Link></button>
        <button><Link to="/about"> ABOUT </Link></button>
        <button><Link to="/products"> PRODUCTS </Link></button>
      </div>

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

export default App;

기본적인 라우터 작성방법을 이용해 App 컴포넌트를 작성했다. 이제 중첩라우팅을 해보자.

About 페이지에 서브페이지를 추가하려면, 아래처럼 자식 Route 컴포넌트를 이용한다.

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

중첩(자식) Route를 작성해서 서브 페이지를 넣고, 서브 페이지의 path는 / 를 생략하고 작성하면 된다. 그리고 element에는 보여줄 컴포넌트를 작성한다.
그리고나서, url(/about/location)을 접속해보자.

아무런 변화가 없을 것이다.

그 이유는

: 📍 Outlet을 이용해 부모 라우트의 컴포넌트에서 자식 라우트 컴포넌트의 위치를 지정해주어야 하기 때문이다.

✏️ Outlet

// About.js 파일

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

function About() {
  return (
    <div>
      <div>
        <h2>여기는 About 페이지입니다.</h2>
      </div>
      <Outlet />
    </div>
  );
}

export default About;

부모 라우트 컴포넌트인 About.js 파일에 Outlet를 import해주고, 서브페이지가 보여질 위치에 Outlet 컴포넌트를 넣어주었다.

그리고나서 url(/about/location)으로 들어가면 location 컴포넌트가 나오는 것을 확인할 수 있다.

Outlet에 이어서, 파라미터와 쿼리를 이용해 url을 변경하고, 변경한 url에 따라 컴포넌트가 다르게 나오도록 할 수 있다.


<Route path="/" element={<Main />}/>
<Route path="/post/1" element={<첫번째 포스팅 />}/>
<Route path="/post/2" element={<두번째 포스팅 />}/>
.
.
.
<Route path="/post/100" element={<백번째 포스팅 />}/>

위의 코드를 보면 Route 컴포넌트를 이용해서 해당 element를 연결한후 일일이 path를 지정한 것을 볼 수 있다. 100개나 되는 포스팅을 연결해줄때 항상 이렇게 작성해야할까?

No.

이렇게 라우팅하는 방법은 '정적 라우팅' 방법이다.

정적 라우팅은 라우터 컴포넌트에서 사용할 경로와 해당 경로로 접속 시 보여줄 컴포넌트를 미리 정의한다. 그래서 규모가 크고 복잡한 어플리케이션은 경로를 미리 설정하는 방식은 사용하기 어렵다.

이럴때 사용하는것이 '동적 라우팅' 방법이다.

동적 라우팅은 url을 미리 정의하지 않고, 특정 규칙을 정의한 후 규칙에 부합한 url이 있을 때 해당 엘리먼트를 보여준다.

동적 라우팅에 대해 알아보자.

✏️ Path Parameter

: 생코님 실습을 이 방법을 통해 실행했다. 이 방법에 대해 자세하게 설명하자면,

Route 컴포넌트의 path 속성에 ' : ' 기호를 사용해 '경로/:문자열' 형태로 url을 설정한다.

// 정적 라우팅
<Route path="/post/" element={<Detail />} />

// 동적 라우팅
<Route path="/post/:문자열" element={<Detail />} />
<Route path="/post/:id" element={<Detail />} />
<Route path="/post/:value" element={<Detail />} />

" : " 기호 뒤, id 와 같은 문자열을 path parameter 라고 한다.
path parameter은 url에 있는 값을 매개변수처럼 사용한다. 그래서 이 parameter를 활용하면 페이지 별로 다른 UI를 보여줄 수 있다.

Route의 path속성에 ":문자열"을 지정하고 url의 :문자열 부분에 값을 넣게 되면, 그 path parameter의 값을 useParams hook을 이용해 얻을 수 있다.

변수를 넣어 훅을 사용해보면 그 변수의 값이 객체인 것을 확인할 수 있다. 이 객체의 key가 Route에서 설정한 path parameter이고 value는 path parameter에 실제 전달된 값이다.

예를 들어 ":id"이면 {id:1}이라는 객체가 나오겠지?

그리고 state 처럼 path parameter의 값이 바뀌면 컴포넌트를 리렌더링 한다.

URL 파라미터가 여러개인 경우엔 /product/:productId/:productName 과 같은 형태로 설정할 수 있다.

💡 v6 이후부터 "중첩라우팅"을 할 때, 하위 페이지가 있을때 부모 Route에 '/*' 을 써도 된다. 그리고 자식 Route path에 부모 경로까지 적을 필요 없이 파라미터만 적어주고(:username) element에 보여줄 하위 컴포넌트를 연결시켜준다. 단, Routes로 다 감싸줘야한다.

✏️ QueryString

: 쿼리 스트링은 요청하는 url에 부가 정보를 포함할 때 사용한다. 기존 URL은 단순한 형태의 요청과 응답을 주고 받지만, 쿼리 스트링을 사용하면 조건에 맞게 정렬된 특정 형태의 정보를 요청하고 받을 수 있다.

예를 들어, 규모가 크고 복잡한 애플리케이션의 상품 종류가 1000개일때, 리스트를 보여주는 페이지에서 천 개의 상품 정보를 한번에 불러와 보여주는 것은 비효율적이다. 실제 유저는 판매량순 상위 10개, 최신순 10개, 리뷰평점순 10개 처럼 특정 기준으로 편집된 정보를 보길원한다.

따라서, 특정 조건에 맞춰서 요청하기 위해 쿼리 스트링을 사용한다.

쿼리 스트링은 문자열이고, key=value 로 표현한다. URL 끝에 "?" 를 넣어서 쿼리스트링의 시작을 표시하고, "&" 로 페어의 구분을 해준다.

// 인기순으로 정렬된 정보
https://www.example.com/products?sort=popular

// 인기순으로 정렬된 정보를 내림차순으로 보고 싶다면
https://www.example.com/products?sort=popular&direction=desc

링크 역할을 하는 컴포넌트에 쿼리스트링이 포함된 주소를 전달하면 된다.

// Link 컴포넌트
<Link to="/list?sort=popular" />

// useNavigate
navigate("/list?sort=popular")

Path parameter 처럼 react-router-dom 에서 쿼리스트링 값을 가져올 수 있는 hook은 "useLocation()", "useSearchParams()" 가 있다.

📍 useLocation()

useLocation hook은 현재의 Location 객체를 반환한다. 그리고 Location 객체의 search 프로퍼티를 보면 ? 기호 다음에 오는 쿼리스트링을 확인할 수 있다.

예제를 한번 보자.

import React from "react";
import { Routes, Route, Link, useLocation } from "react-router-dom";
import Home_shoppingmall from "./components/Home_shoppingmall";
import About_shoppingmall from "./components/About_shoppingmall";
import Products_shoppingmall from "./components/Products_shoppingmall";
import Location from "./components/Location";

function Shoppingmall() {
  return (
    <div className="Shoppingmall">
      <div>
        <button>
          <Link to="/"> HOME </Link>
        </button>
        <button>
          <Link to="/about"> ABOUT </Link>
        </button>
        <button>
          <Link to="/products"> PRODUCTS </Link>
        </button>
        <button>
          <Link to="/list"> LIST</Link>
        </button>
      </div>
      <Routes>
        <Route path="/" element={<Home_shoppingmall />}></Route>
        <Route path="about" element={<About_shoppingmall />}>
          <Route path="location" element={<Location />}></Route>
        </Route>
        <Route path="/products/*" element={<Products_shoppingmall />}></Route>
        <Route path="/list" element={<List_popular />}></Route>
      </Routes>
    </div>
  );
}

function List_popular() {
  const location = useLocation();
  console.log(location.search);

  return (
    <div>
      <button>
        <Link to="/list?sort=popular">popular</Link>
      </button>
      <Routes>
        <Route path="?sort=popular"></Route>
      </Routes>
    </div>
  );
}

export default Shoppingmall;

Location 객체의 프로퍼티 정보들을 보면,

  • hash : 주소의 #문자열 뒤의 값
  • pathname : 현재 주소 경로
  • search : ?를 포함한 쿼리스트링
  • state : 페이지로 이동시 임의로 넣을 수 있는 상태 값
  • key : location 객체의 고유 값, 초기값은 default, 페이지가 변경될 때 마다 고유의 값이 생성된다.
http://localhost:3000/product/1?search=productName&q=demo#test

이 url을 입력하면,

  • hash: #test
  • pathname: /product/1
  • search: ?search=productName&q=demo
  • state:
  • key: default

의 결과를 알 수 있다.

그리고 위의 실습예제 콘솔창을 확인하면 location.search의 값이 ?sort=popular 인 것을 확인 할 수 있다.

이렇게 가져온 값에서 'popular'만 뽑아서 사용하려면 별도의 작업을 해야하는데 이는 복잡하다. 여러개의 페어가 존재한다면 더욱 복잡할 것이다. 그래서 이럴 때 다양한 메서드를 제공해 원하는 값을 가져올 수 있게 하는 것이 URLSearchParams 이라는 객체인데, react-router-dom은 이 객체를 반환해주는 useSearchParams 라는 훅을 제공한다.

📍 useSearchParams()

const [searchParams, setSearchParams] = useSearchParams();

SearchParams 메서드로 배열을 받아올 수 있는데, 두 개의 프로퍼티들을 가져올 수 있다. (이름은 자유)

  • searchParams : 쿼리 스트링의 key에 대한 value를 가져온다. URLSearchParams 객체이면서 쿼리 스트링을 다루기 위한 여러 메서드를 제공한다.

    • 값을 읽어오는 메서드
    1. searchParams.get(key) - 특정한 key의 value를 가져오는 메서드, 해당 key 의 value 가 두개라면 제일 먼저 나온 value 만 리턴
    2. searchParams.getAll(key) - 특정 key 에 해당하는 모든 value 를 가져오는 메서드
    3. searchParams.toString() - 쿼리 스트링을 string 형태로 리턴
      .
    • 값을 변경하는 메서드
    1. searchParams.set(key, value) - 인자로 전달한 key 값을 value 로 설정, 기존에 값이 존재했다면 그 값은 삭제됨

    2. searchParams.append(key, value) - 기존 값을 변경하거나 삭제하지 않고 추가하는 방식

      ex)
      const userId = searchParams.get('userId');
      const docId = searchParams.get('docId');

    searchParams 을 변경하는 메서드로 값을 변경해도 실제 url 의 쿼리 스트링은 변경되지 않는데, 이를 변경하려면 setSearchParams 에 searchParams 를 인자로 전달해야 한다.

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

    ex)
    setSearchParams({ key: 'value' });

예를 보자. 먼저 useSearchParams를 import 한다.

import { useSearchParams } from 'react-router-dom';
const [searchParams, setSearchParams] = useSearchParams();

const setSortParams = () => {
	searchParams.set('sort', 'clear');
	setSearchParams(searchParams);
};

const appendSortParams = () => {
	searchParams.append("sort", "hello-world");
	setSearchParams(searchParams);
};

useSearchParams 를 선언하고 setSortParams 함수를 만들고, set 메서드를 사용해 key는 sort, value는 clear을 설정한다. 그리고 setSearchParams의 인자에 searchParams를 넣어 현재 url의 쿼리 스트링을 변경한다.

append 메서드를 사용하면 기존 값을 유지하며 sort 라는 키에 hello-world 라는 밸류를 추가할 수 있다.

✏️ 파라미터 vs 쿼리

: 그럼 둘 중에 하나를 언제 어떻게 사용할까?

쿼리 스트링은 필터링할때 많이 사용하고, 파라미터는 특정한 자원에 접근할때 사용한다.
일반적으로는 파라미터는 특정 id 나 이름을 가지고 조회를 할 때 사용하고, 쿼리의 경우엔 어떤 키워드를 검색하거나, 요청을 할 때 필요한 옵션을 전달 할 때 사용된다.


출처

https://velog.io/@reasonz/2022.07.14-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%9D%BC%EC%9A%B0%ED%84%B0-%EC%A4%91%EC%B2%A9-%EB%9D%BC%EC%9A%B0%ED%8C%85-nested-routes-outlet
https://kyung-a.tistory.com/36
https://dev.to/tywenk/how-to-use-nested-routes-in-react-router-6-4jhd
https://react.vlpt.us/react-router/02-params-and-query.html
https://jinyisland.kr/post/react-router/
https://goddaehee.tistory.com/305?category=395445

https://velog.io/@rayong/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%8F%99%EC%A0%81-%EB%9D%BC%EC%9A%B0%ED%8C%85-useParams
생활코딩! 리액트 프로그래밍 책

profile
프론트엔드개발자가 되고 싶어서 열심히 땅굴 파는 자

0개의 댓글