[React] React Router

찐새·2022년 8월 11일
0

React

목록 보기
4/21
post-thumbnail

React Router Dom

react-router-dom v6

install

  • npm i react-router-dom
    • react-router-domv6 이전 버전을 쓰고 싶다면 npm i react-router-bom@version
    • ex) npm i react-router-bom@5.3.0
    • react-router-domv6 참고글

Router 설정

// Router.tsx
import { BrowserRouter, Route, Routes } from "react-router-dom";
import Post from "./routes/Post";
import Posts from "./routes/Posts";

function Router() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Posts />} />
        <Route path=":postId" element={<Post />} />
      </Routes>
    </BrowserRouter>
  );
}

export default Router;
  • BrowserRouter는 URL history를 스택으로 저장하여 탐색을 도와주는 인터페이스이다.
  • Routes는 v5의 Switch 역할이며, Route의 요소를 살펴 가장 일치하는 항목을 찾아 UI에 렌더링한다.
  • Routeelement에 할당된 컴포넌트와 path를 렌더링한다.
    • 경로는 컴포넌트 내부에 위치할 수 있다.
  • path:postId는 개별 경로의 파라미터로 작동한다.
    • 베이스가 /post이고 post id가 1인 경우 URL은 /post/1이 된다.
  • useParams를 사용해 하위 컴포넌트의 파라미터를 가져올 수 있다.
import { useParams } from "react-router-dom";

function Post() {
  const params = useParams();
  console.log(params);
  // {postId: '1'}
  return (...rendering);
}

export default Post;
  • id가 1인 Post 컴포턴트에 접근했다면 params{postId: '1'}를 가져온다.
  • <a>는 클릭 시 페이지를 새로고침하기 때문에 <Link>를 사용한다.
const posts = [
  {
    id: "1",
    name: "js",
    content: "Javascript",
    rank: 1,
  },
  {
    id: "2",
    name: "ts",
    content: "Typescript",
    rank: 2,
  },
  {
    id: "3",
    name: "ps",
    content: "Pyscript",
    rank: 3,
  },
];

function Posts() {
  return (
    <div>
      {posts.map((post) => (
        <Post key={post.id}>
          <Link to={`/${post.id}`}>{post.name}</Link>
        </Post>
      ))}
    </div>
  );
}

export default Posts;
  • toRoutepath에 매칭되는 컴포넌트로 가는 경로이다.
  • 클릭하면 id가 1인 post의 내용을 담고 있는 <Post /> 컴포넌트 페이지로 이동한다.

Behind the Scene

  • 상위 페이지에서 하위 페이지로 이동할 때 새로 데이터를 로드하는 것은 비효율적인 방법이다.
    • 이미 전체 페이지에서 로드한 데이터가 있기 때문이다.
  • Behind the Scene은 상위 페이지의 데이터를 하위 페이지로 미리 넘겨주는 것이다.
function Posts() {
  return (
    <div>
      {posts.map((post) => (
        <Post key={post.id}>
          <Link
            to={`/${post.id}`}
            state={{
              name: post.name,
              content: post.content,
              rank: post.rank,
            }}
          >
            {post.name}
          </Link>
        </Post>
      ))}
    </div>
  );
}

export default Posts;
  • state 속성에 넘겨줄 데이터를 담는다.
  • Post 컴포넌트에서 useLocation()을 통해 받은 데이터를 확인할 수 있다.
import { useLocation } from "react-router-dom";

function Post() {
  const loc = useLocation()
  console.log(loc)
  return (...rendering);
}

export default Post;

// 출력
hash: ""
key: "blabla"
pathname: "/1"
search: ""
state: {name: 'js', content: 'Javascript',rank: 1}
[[Prototype]]: Object
  • 상위 페이지에서 전달한 데이터가 state에 모두 담겨 있다.
  • ts는 담긴 데이터들의 타입을 모르므로 정의한다.
  • v6에서는 Generic을 지원하지 않는다고 한다. 따라서 as를 사용했다.
interface StateTypes {
  state: {
    name: string;
    content: string;
    rank: number;
  };
}

function Post() {
  const { state } = useLocation() as StateTypes;
  // v5
  // const { state } = useLocation<StateTypes>();
  return (
    <div>
      <span>{state.name}</span>
      <span>{state.content}</span>
      <span>{state.rank}</span>
    </div>
  );
}

export default Post;
  • 이렇게 만들어진 페이지는 하위 페이지 주소로 직접 접속 시 페이지를 보여주지 않는다.
  • 상위 페이지에서 데이터를 요청해 url에 담지만, 직접 접속한 하위 페이지는 데이터를 요청하지 않아 url이 비어있기 때문이다.

Nested Route

  • 컴포넌트 내에 위치하는 경로를 Nested Route라고 한다.
  • Post 컴포넌트 내에서 RepliesMembers를 이동하는 Route를 설정할 수 있다.
  • v6에서는 두 가지 버전으로 Nested Route를 구성할 수 있다.

첫 번째

  • Router와 분리하여 부모 컴포넌트에 Route를 추가하는 방법이다.
// Router.tsx
(...imports)

function Router() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Posts />} />
        <Route path=":postId*" element={<Post />} />
      </Routes>
    </BrowserRouter>
  );
}

export default Router;

// Post.tsx
import { Route, Routes } from "react-router-dom";
import Replies from "./Replies";
import Members from "./Members";

function Post() {
  return (
    <div>
       <Routes>
          <Route path="replies" element={<Replies />} />
          <Route path="members" element={<Members />} />
        </Routes>
    </div>
  );
}

export default Post;

Router.tsx

  • 자식 path를 가지려면 부모 컴포넌트 path*를 추가해야 한다.

Nested

  • 부모 컴포넌트 내에 Router.tsx처럼 Route를 구성한다.

두 번째

  • 경로를 Router.tsx로 몰아 넣고, 자식 path가 들어갈 자리에 Outlet을 위치시킨다.
// Router.tsx
(...imports)

function Router() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Posts />} />
        <Route path=":postId*" element={<Post />} />
        <Route path="replies" element={<Replies />} />
        <Route path="members" element={<Members />} />
      </Routes>
    </BrowserRouter>
  );
}

export default Router;

// Post.tsx
import { Outlet } from "react-router-dom";
import Replies from "./Replies";
import Members from "./Members";

function Post() {
  return (
    <div>
      <Outlet />
    </div>
  );
}

export default Post;
  • Outlet이 위치한 컴포넌트 path에 자식 path를 적으면 url은 추가되나 페이지 변경은 일어나지 않는다.

createBrowserRouter

  • 라우트를 좀 더 선언적으로 생성할 수 있는 함수
import { createBrowserRouter } from "react-router-dom";
import Root from "./Root";
import About from "./screens/About";
import Home from "./screens/Home";
import Other from "./screens/Other";
import NotFound from "./screens/NotFound";
import ErrorComponent from "./components/ErrorComponent";

const router = createBrowserRouter([
  {
    path: "/",
    element: <Root />,
    children: [
      {
        path: "",
        element: <Home />,
    	errorElement: <ErrorComponent />,
      },
      {
        path: "about",
        element: <About />,
      },
    ],
    errorElement: <NotFound />,                                   
  },
  {
    path: "/other",
    element: <Other />,
  },
]);

export default router;
  • 기존 Router.tsxBrowserRoutercreateBrowserRouter로 수정한다.
  • createBrowserRouter의 인자는 라우팅할 경로와 컴포넌트 객체로 이뤄진 배열이다.
  • 배열의 객체는 상위 경로이며, 각 객체 내부의 children 프로퍼티는 하위 경로이다.
  • 하위 경로는 상위 경로 컴포넌트의 요소를 상속한다.
    • ex) <Root /><Navigation /><Home /><About />은 볼 수 있지만, <Ohter />는 다른 상위 경로이므로 볼 수 없다.
  • errorElement는 페이지에 문제가 생겼을 때 보여줄 화면을 렌더링한다.
    • 상위 경로 errorElement<NotFound />를 렌더링했을 경우, 잘못된 경로 접근 시 동작한다.
    • 하위 경로 errorElement<ErrorComponent />를 렌더링했을 경우, 해당 경로에서 문제가 발생했을 시 동작한다.
    • errorElement가 없다면 컴포넌트가 충돌했을 때 앱을 죽여버리기 때문에 보호를 위해서 작성하는 것이 좋다.
import React from "react";
import ReactDOM from "react-dom/client";
import { RouterProvider } from "react-router-dom";
import router from "./Router";

ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
  <React.StrictMode>
    <RouterProvider router={router} />
  </React.StrictMode>
);
  • index 컴포넌트에 <RouterProvider router={router} />를 추가한다.
import { Outlet } from "react-router-dom";
import Navigation from "./components/Navigation";

function Root() {
  return (
    <div>
      <Navigation />
      <Outlet />
    </div>
  );
}

export default Root;
  • <Outlet />을 통해 하위 컴포넌트가 렌더링할 위치를 정한다.

useNavigate

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

function Navigation() {
  const navigate = useNavigate();
  const onAboutClick = () => {
    navigate("/about");
  };
  return (
    <header>
      <ul>
        <li>
          <Link to={"/"}>Home</Link>
        </li>
        <li onClick={onAboutClick}>About</li>
      </ul>
    </header>
  );
}

export default Navigation;
  • 라우트를 조작할 수 있는 함수로 <Link>를 대신할 수 있다.
  • to를 인자로 받으며, 해당 경로 외에 -1뒤로가기를 의미한다.

useParams

import { createBrowserRouter } from "react-router-dom";
import Root from "./Root";
import Home from "./screens/Home";
import User from "./screens/User";

const router = createBrowserRouter([
  {
    path: "/",
    element: <Root />,
    children: [
      {
        path: "",
        element: <Home />,
      },
      {
        path: "users/:userId",
        element: <User />,
      },
    ],                                 
  },
]);

export default router;
  • :{bla}로 설정한 경로는 URL의 파라미터가 된다.
import { useParams } from "react-router-dom";

function User() {
  const { userId } = useParams();
  return <h1>User {userId}</h1>;
}

export default User;
  • useParams를 통해 파라미터를 받아와 활용할 수 있다.

useMatch

  • 현재 url에 내가 원하는 path가 담겨 있는지 확인할 수 있는 기능이다.
  • 찾는 path가 있다면 url 정보는 반환하고, 아니라면 null을 반환한다.
import { useMatch } from "react-router-dom";
function Post() {
  const match = useMatch("/post");
  return (
    <div>
      <h1>Here is Post Component</h1>
    </div>
  );
}

export default Post;

Outlet

  • Outlet은 위에서 언급했듯, 하위 경로를 렌더링한다.
// Router.tsx
const router = createBrowserRouter([
  {
	// (...)
      {
        path: "users/:userId",
        element: <User />,
    	children: [
          {
            path: "followers",
            element: <Followers />,
          },
        ],
      },
    ],                                 
  },
]);

// User.tsx
function User() {
  // (...)
  return (
    <div>
      <h1>User {params.userId}</h1>
      <hr />
      <Link to="followers">See Followers</Link>
      <Outlet />
    </div>
  );
}
  • Link/{path}를 사용하면 절대경로로 이동한다. 예를 들어, /followers를 사용하면 http://localhost:3000/followers를 가리킨다.
    • /없이 {path}를 사용하면 해당 경로의 하위 경로로 추가된다.

useOutletContext

import { Link, Outlet, useParams } from "react-router-dom";
import { users } from "../../db";

function User() {
  const { userId } = useParams();
  return (
    <div>
      <h1>
        User {userId} Name : {users[Number(userId) - 1].name}
      </h1>
      <hr />
      <Link to="followers">See Followers</Link>
      <Outlet
        context={{
          nameOfMyUser: users[Number(userId) - 1].name,
        }}
      />
    </div>
  );
}

export default User;
  • Outletcontext 어트리뷰트에 상태를 입력하면 하위 경로에서 해당 상태를 불러와 사용할 수 있다.
import { useOutletContext } from "react-router-dom";

interface FollowersContextProps {
  nameOfMyUser: string;
}

function Followers() {
  const { nameOfMyUser } = useOutletContext<FollowersContextProps>();
  return <h1>{nameOfMyUser}&apos;s Followers</h1>;
}

export default Followers;
  • useOutletContext는 상위 경로에서 보낸 상태를 포함한다.
  • 해당 상태는 모든 하위 경로에서 사용할 수 있다.

useSearchParams

  • URL의 query를 가져올 때 사용한다.
import { useSearchParams } from "react-router-dom";

function Home() {
  const [readSearchParams, setSearchParams] = useSearchParams();
  console.log(readSearchParams.has("query")); // false
  setSearchParams({ query: "search" }); // http://localhost:3000/?query=search
  console.log(readSearchParams.get("query")); // search
  return (
    <div>
      // (...)
    </div>
  );
}

export default Home;
  • useSearchParamsuseState와 비슷하게 사용한다.
  • readSearchParams는 URL에 담긴 query에 대한 접근을 도와준다.
    • JavaScriptURLSearchParams 인스턴스를 호출하고, 해당 인스턴스의 메서드를 사용한다.
  • setSearchParams는 URL query를 설정한다.
    • 인자로 받은 객체가 query의 키-값이 된다.

참고
노마드 코더 - React JS 마스터클래스
React Router v6 Docs
@soryeongk님 velog - React Router v6 변경점

profile
프론트엔드 개발자가 되고 싶다

0개의 댓글