React Router v6.8.2 사용법

jung_ho9 개발일지·2023년 3월 8일
2

React

목록 보기
24/28
post-thumbnail
post-custom-banner

https://ko.reactjs.org/

이번 프로젝트를 진행하면서 또 하나의 도전, react-router 최신 버전 v6.8.2 사용해보기! 팀원 중 한 분이 이전의 프리 프로젝트를 진행하면서 최신 버전의 react-router를 사용해보셨는데 사용 후 긍정적인 반응을 보여주셔서 각자 공부해보기로 했다.

🎇 참고 문서

라우터-튜토리얼

라우터 추가


가장 먼저 해야 할 일은 브라우저 루트 라우터를 생성하고 루트 경로를 연결하는 것으로 그 방법은 아래 내용과 같다.

createBrowserRouter 를 사용해 router 를 생성하고 path에 경로를 지정하고 element로 해당 경로가 URL과 일치할 때 렌더링할 요소를 지정한다.

main.jsx
import { createBrowserRouter } from "react-router-dom";

const router = createBrowserRouter([
  {
    path: "/",
    element: <Root />,
    loader: rootLoader,
  },
]);

Error page 오류 처리


커스텀한 에러 페이지를 생성했다면, errorElement 에 해당 페이지를 전달해준다.

"/" 루트 경로에서의 에러 페이지를 의미한다.

main.jsx
import ErrorPage from "./error-page";

const router = createBrowserRouter([
  {
    path: "/",
    element: <Root />,
    errorElement: <ErrorPage />,
  },
]);

더 친절한 에러페이지를 제작하고 싶다면 useRouteError를 사용하여 발생한 오류의 응답을 가져와 보여줄 수 있다.

단, 이 기능은 데이터 라우터를 사용하는 경우에만 동작한다.

error-page.jsx 

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

export default function ErrorPage() {
  const error = useRouteError();
  console.error(error);

  return (
    <div id="error-page">
      <h1>Oops!</h1>
      <p>Sorry, an unexpected error has occurred.</p>
      <p>
        <i>{error.statusText || error.message}</i>
      </p>
    </div>
  );
}

중첩 라우트


children : routes와 같은 라우트 설정 객체의 또 다른 배열
아래 코드와 같이 / 루트 경로에서 children 배열 안에 있는 events/:id 경로로 이동했을 시 element에 적은 컴포넌트가 Root 컴포넌트 안에 있는 Outlet 부분에 렌더링 된다.

즉, 중첩 된 URL을 사용하지 않고도 컴포넌트 중첩을 사용할 수 있게 된다.


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

const router = createBrowserRouter([
  {
    path: "/",
    element: <Root />,
    loader: rootLoader,
    children: [
      {
        path: "events/:id",
        element: <Event />,
        loader: eventLoader,
      },
      {
        path: "posts/:id",
        element: <Post />,
        loader: eventLoader,
      },
    ],
  },
]);
import { Outlet } from "react-router-dom";

export default function Root() {
  return (
    <>
      {/* all the other elements */}
      <div id="detail">
        <Outlet />
      </div>
    </>
  );
}

데이터 API (v6.4 이상만 가능)


이번 6.4버전 이상에서 가장 주목할 특징은 loaders, actions, fetchers 라는 data api를 제공한다는 점이다. 이 api는 서버 측 렌더링에 대한 지원을 제공하고 라우팅 기능을 사용하여 데이터를 불러오는 방법을 간편하게 만들어준다.

Loader


로더는 컴포넌트가 렌더링되기 전에 호출되며 로더 함수가 값을 리턴하면 useLoaderData()로 컴포넌트에서 데이터를 받아 사용할 수 있다.

아래 코드로 예를 들면, Root 컴포넌트를 표시하려는 시점에는 useLoaderData로 리턴된 데이터를 이미 사용할 수 있는 것이다. 이는 사용자 경험을 향상시킬 뿐만 아니라. 동시에(co-located) 데이터 가져오기 및 렌더링과 관련된 개발자 경험을 개선할 수 있다.

아래와 같이 각 route파일에 loader라는 함수를 만든뒤 이를 export하여 사용하는것이 일반적이다.

src/routes/root.jsx

import { Outlet, Link } from "react-router-dom";
import { getContacts } from "../contacts";

export async function loader() {
  const contacts = await getContacts();
  return { contacts };
}

작성한 로더함수를 loader에 불러온다.

src/routes/main.jsx
import Root, { loader as rootLoader } from "./routes/root";

const router = createBrowserRouter([
  {
    path: "/",
    element: <Root />,
    errorElement: <ErrorPage />,
    loader: rootLoader,
  },
]);

로더에서 리턴된 값을 컴포넌트에서 useLoaderData()로 받아와 매핑한다.

src/routes/root.jsx

import { Outlet, Link } from "react-router-dom";
import { getContacts } from "../contacts";

/* other code */

export default function Root() {
  const { contacts } = useLoaderData();
  return (
    <>
      <div id="sidebar">
        <h1>React Router Contacts</h1>
        {/* other code */}

        <nav>
          {contacts.length ? (
            <ul>
              {contacts.map((contact) => (
                <li key={contact.id}>
                  <Link to={`contacts/${contact.id}`}>
                    {contact.first || contact.last ? (
                      <>
                        {contact.first} {contact.last}
                      </>
                    ) : (
                      <i>No Name</i>
                    )}{" "}
                    {contact.favorite && <span></span>}
                  </Link>
                </li>
              ))}
            </ul>
          ) : (
            <p>
              <i>No contacts</i>
            </p>
          )}
        </nav>

        {/* other code */}
      </div>
    </>
  );
}

Action


우리가 action을 배울때 주목해야할 부분은 HTML form이다. HTML form은 특정 url에 데이터를 전송해서 처리하는 요청과정이다. 그리고 그 요청을 처리할 주소값은 보통 action에다가 정의한다.

그럼 리액트 라우터는 이를 처리하기 위해 Form이라는 것을 사용하고 이는 html form을 모방하여 리퀘스트를 날린다.

<from><Form> 을 헷갈릴 수 있지만, <form>을 사용하면 서버에다가 리퀘스트를 날리는 것이고 <Form>을 사용하면 클라이언트 측에다가 리퀘스트를 날리는 것이다.

그리고 클라이언트측에서 리퀘스트를 받았다면, 이는 action에서 처리하고 사용방법은 아래와 같다.

<Form>을 사용하고 action 함수를 정의한다.

src/routes/root.jsx
import {
  Outlet,
  Link,
  useLoaderData,
  Form,
} from "react-router-dom";
import { getContacts, createContact } from "../contacts";

export async function action() {
  await createContact();
}

/* other code */

export default function Root() {
  const { contacts } = useLoaderData();
  return (
    <>
      <div id="sidebar">
        <h1>React Router Contacts</h1>
        <div>
          {/* other code */}
          <Form method="post">
            <button type="submit">New</button>
          </Form>
        </div>

        {/* other code */}
      </div>
    </>
  );
}

라우터에 action을 연결한다.

src/main.jsx 
import {
  Outlet,
  Link,
  useLoaderData,
  Form,
} from "react-router-dom";
import { getContacts, createContact } from "../contacts";

export async function action() {
  await createContact();
}

/* other code */
import Root, {
  loader as rootLoader,
  action as rootAction,
} from "./routes/root";

const router = createBrowserRouter([
  {
    path: "/",
    element: <Root />,
    errorElement: <ErrorPage />,
    loader: rootLoader,
    action: rootAction,
    children: [
      {
        path: "contacts/:contactId",
        element: <Contact />,
      },
    ],
  },
]);

주의할 점! action은 post로 보내야 호출된다. get으로 보내면 loader가 불린다.

URL 검색 매개변수


인터페이스 URLSearchParams는 URL의 쿼리 문자열과 함께 작동하는 유틸리티 메서드를 정의할 수 있다.

이름을 검색한다고 하면 쿼리스트링의 값은 name의 속성값에 따라 달라지겠지만, 아래 예시와 같이 name 속성값을 q라고 하고 name이 jungho인 사람을 찾는다고 가정하면 ?search=jungho 으로 표시될 것이다.

src/routes/root.jsx
<Form id="search-form" role="search">
  <input
    id="bn"
    aria-label="Search contacts"
    placeholder="Search"
    type="search"
    name="q"
  />
  <div id="search-spinner" aria-hidden hidden={true} />
  <div className="sr-only" aria-live="polite"></div>
</Form>

이때 쿼리스트링 값을 따로 파싱하고 싶다면 URLSearchParams을 사용하여 더욱 쉽게 다를 수 있다.

export async function loader({ request }) {
  const url = new URL(request.url);
  const q = url.searchParams.get("q");
  const contacts = await getContacts(q);
  return { contacts };
}
profile
꾸준하게 기록하기
post-custom-banner

1개의 댓글

comment-user-thumbnail
2023년 8월 19일

와 진짜 설명 잘하시는 것 같습니다

답글 달기