[react]react-router-dom v6

누리·2023년 3월 30일
1

React Foundation

목록 보기
17/18

react-router-dom 버전5에서 버전6로 업데이트 되면서 달라진 점을 알아보자.

1. BrowserRouter

본래 Switch를 사용하여 Route로 컴포넌트를 각각 감싸주었다면 버전6에서는 RoutesRoute를 모두 감싸준다.
훨씬 간결해진 것을 볼 수 있다.

// ./Router.tsx
// 버전5
// import {BrowserRouter, Switch, Route} from "react-router-dom";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Home from "./components/Home";
import About from "./components/About";

const Router = () => {
  return (
    <BrowserRouter>
      /** 버전5
      <Switch>
    	<Route path="/about">
    	  <About />
    	</Route>
    	<Route path="/">
    	  <Home />
    	</Route>
      </Switch>
      **/
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
      </Routes>
    </BroswerRouter>
  )
}

2. createBrowserRouter

createBrowserRouter는 react router v6.4 부터 사용 할 수 있다. 이를 사용함으로써 브라우저를 좀 더 선언적으로 바꿔줄 수 있다.(JSX 컴포넌트를 사용하지 않고 객체의 형태로 보여줌)

createBrowserRouter()

createBrowserRouter()에 라우팅 할 path와 element를 작성할 수 있다. children 속성으로 배열에 중첩된 라우터(Nested Router)를 추가해 줄 수 있다.

// Router.tsx
import { createBrowserRouter } from 'react-router-dom';
import About from './pages/About';
import Home from './pages/Home';
import App from './App';

const router = createBrowserRouter([
  {
    path: '/',
    element: <App />,
    children: [
      {
        path: '',
        element: <Home />,
      },
      {
        path: 'about',
        element: <About />,
      },
    ],
  },
]);

export default router;
  • 위의 사용된 Router 컴포넌트를 사용하지 않고, router 변수를 선언한다.
  • createBrowserRouter를 사용함으로써 Router를 배열 형식으로 표현할 수 있게 된다.
  • 첫번째 객체안에 들어갈 내용은 <Home />페이지가 아닌 전체 Route들의 컨테이너 역할(<App />)이라고 보면 된다.
  • <App />의 path인 /를 부모로 생각하고 자식 경로를 짠다고 생각하자.

<RouterProvider />

<BrowserRouter>로 감싸지 않고 최상단에서 index.tsx에서RouterProvider 를 import한다.
이 때<RouterProvider />에는 router={} props를 필수로 넣어야 한다.

// index.tsx

import { RouterProvider } from 'react-router-dom';
import router from './Router';

root.render(
  <React.StrictMode>
    <RouterProvider router={router} />
  </React.StrictMode>,
);
  • 본래의 <App /> 컴포넌트가 들어갈 자리에 <BrowserRouter />컴포넌트를 대입한다.
  • router={}에는 createBrowserRouter() 함수로 생성한 router를 props로 넘겨준다.

<Outlet />

RouterProvider에 router를 넘기면, root 경로인 <App /> 컴포넌트만 출력된다. 하위 경로(/ + "", / + about)에 알맞는 컴포넌트를 출력하기 위해서 App.tsx에 을 추가한다.

// App.tsx

import { Outlet } from 'react-router-dom';
import Header from './Components/Header';

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

export default App;

아래에서 Outlet에 대한 내용을 더 다뤄볼 예정이다.

3. errorElement

V6로 되면서 가장 큰 변화중의 하나로 에러를 효율적으로 관리할 수 있다.
컴포넌트에 에러가 발생해서 충돌하거나, 컴포넌트의 위치를 찾지못할 때 이를 눈으로 보여줌으로써 에러관리를 용이하게 할 수 있다.

// NotFound.tsx

function NotFound() {
  return (
    <h1>404 Not found</h1>
  );
}

export default NotFound;
// Crash.tsx

function Crash() {
  return (
    <h1>This component crashed</h1>
  );
}

export default Crash;
  • 에러 컴포넌트 파일을 생성하고 해당에러가 발생했을 때, 보여질 화면을 작성한다.
  • Router.tsx에서 errorElement에 만든 에러 컴포넌트를 작성한다.

case1 컴포넌트의 위치를 찾지 못 할 때 (자식경로를 못찾았을 때)

<App />부모 패스인 /를 기준으로 잘못된 자식경로로 들어가게 되면 앞서 작성한 NotFound 컴포넌트의 화면이 보여지게 된다.

// Router.tsx
import { createBrowserRouter } from 'react-router-dom';
import About from './pages/About';
import Home from './pages/Home';
import App from './App';
import NotFound from './error/NotFound';

const router = createBrowserRouter([
  {
    path: '/',
    element: <App />,
    children: [...
    ],
    errorElement: <NotFound />
  },
]);

export default router;

case2 컴포넌트가 충돌했을 때

각각의 페이지를 보여주는 컴포넌트마다 충돌했을때의 에러 컴포넌트를 작성해 주면 다른컴포넌트에 영향주지 않고 해당 에러가 발생한 컴포넌트만을 손쉽게 찾을 수 있다.

// Router.tsx
import { createBrowserRouter } from 'react-router-dom';
import About from './pages/About';
import Home from './pages/Home';
import App from './App';
import Crash from './error/Crash';

const router = createBrowserRouter([
  {
    path: '/',
    element: <App />,
    children: [
      {
        path: '',
        element: <Home />,
    	errorElement: <Crash />
      },
      {
        path: 'about',
        element: <About />,
      },
    ],
  },
]);

export default router;
  • <Home /> 컴포넌트에서 충돌이 발생했을 때, <About />에는 영향을 주지 않는다. Home의 경로에 있을때만 'This component crashed' 문구가 보여지게 된다. 또한 네비게이션이 살아있다.
  • 에러컴포넌트를 작성하지 않았다면 그냥 빈 화면이 송출되므로 에러핸들링에 어려움이 있을 수 있다.

4. useNavigate

다른페이지로 이동시키는 이 훅은 Link와는 조금 다른점이 있다.

  • 클릭 시 바로 이동하는 로직 구현 시에 사용
  • ex) 상품 리스트에서 상세 페이지 이동 시

useNavigate

  • 페이지 전환 시 추가로 처리해야 하는 로직이 있을 경우 useNavigate 사용
  • ex) 로그인 버튼 클릭 시
    회원가입 되어 있는 사용자 -> Main 페이지로 이동
    회원가입이 되어 있지 않은 사용자 -> SignUp 페이지로 이동
// Header.tsx
import { Link, useNavigate } from "react-router-dom";

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

export default Header;

5. useParams

UseParams는 Parameter(파라미터) 값을 URL을 통해서 넘겨서 넘겨받은 페이지에서 사용할 수 있도록 도와준다.
예를 들어, 여러 개의 영화 정보가 담겨있는 데이터를 출력해준다고 가정할 때, 영화 제목을 클릭해서 세부 페이지로 이동을 하도록 구현한다면, 영화의 id 값을 URL로 넘겨 세부 페이지에 id 값에 해당하는 영화 정보만 출력하도록 만들 수 있도록 도와준다.
참고로 파라미터가 아닌 현재 페이지의 Pathname을 가져오려면 useLocation()을 사용해야 한다.

간단한 예시코드를 통해 알아보자

  • 유저데이터베이스를 생성한다.

    // db.ts
    export const users = [
      {
        id: 1,
        name: "nuri",
      },
      {
        id: 2,
        name: "sihyun",
      }
    ];
  • 이 유저들을 내 홈페이지에 렌더하게 하려면 어떻게 해야할까.

    // Home.tsx
    import { users } from "../db";
    import { Link } from "react-router-dom";
    
    function Home () {
      return (
        <div>
          <h1>Users</h1>
          <ul>
            {users.map((user) => (
              <li key={user.id}>
                <Link to={`/users/${user.id}`}>
                  {user.name}
                </Link>
              </li>
            ))}
          </ul>
        </div>
      )
    }
  • User 페이지를 새로 만들고 파라미터를 생성한다.

    // User.tsx
    import { useParams } from 'react-router-dom';
    import { users } from '../db'; 
    function User () {
      const { userId } = useParams();
      return(
        <h1>
          {userId}id's user is named : {users[Number(userId)-1].name}
        </h1>
      );
    }
    export default User;
  • User 페이지로 렌더할 수 있도록 router를 생성한다.
    이때, 파라미터의 이름을 User 페이지의 useParams로 불러온 파라미터 변수명과 일치 시켜준다.

    // Router.tsx
    import { createBrowserRouter } from 'react-router-dom';
    import Home from './pages/Home';
    import App from './App';
    import User from './User';
    
    const router = createBrowserRouter([
      {
        path: '/',
        element: <App />,
        children: [
          {
            path: '',
            element: <Home />,
          },
          {
            path: 'user/:userId',
            element: <User />
          },
        ],
      },
    ]);
    
    export default router;

    이때 :userId 는 url주소에서 확인할 수 있는데, http://localhost:3000/users/1에서 1에 해당한다.

    그리고 스크린에는 User 컴포넌트에서 생성한대로
    1id's user is named : nuri 라는 화면을 볼 수 있다.

6. Outlet

레이아웃 설정이 매우 간단해졌다. 중첩 라우팅의 구성이 되면 Outlet을 통해서 상위의 컴포넌트를 레이아웃화 할 수 있다.

이전에는 {children}을 사용해서 layout을 구성한다음에 라우트에 항상 레이아웃과 연결짓도록 했는데 <Outlet /> 을 사용하면 {children} 을 사용하는 것과 같은 효과가 난다!

간단한 예시코드를 통해 알아보자

  • User 스크린에 자식이 있다면 <Outlet />이 이 스크린의 자식들을 render 하게된다.

    // User.tsx
    import { useParams, Outlet, Link } from 'react-router-dom';
    import { users } from '../db'; 
    function User () {
     const { userId } = useParams();
      return(
        <div>
          <h1>
            {userId}id's user is named : {users[Number(userId)-1].name}
          </h1>
          <hr />
          <Link to="followers">see followers </Link>
          <Outlet />
        </div> 
      );
    }
    export default User;

    이때 Link 속성의 to의 입력값으로 "/followers 가 아닌 "followers"로 해야하는데 그 이유는 /가 절대경로 만들어 버리기 때문이다.
    절대경로로 설정했을때, 우리가 이동하는 주소는 http://localhost:3000/followers가 되기때문에 /를 빼줌으로써 상대경로로 설정하면 된다.

  • User의 자식 스크린을 만들어 보자

    // Followers.tsx
    function Followers () {
      return (
        <h1>Followers</h1>
      );
    }
    export default Followers;
  • Router에 User 스크린의 자식 컴포넌트를 추가해 보자

    // Router.tsx
    import { createBrowserRouter } from 'react-router-dom';
    import Home from './pages/Home';
    import App from './App';
    import User from './User';
    import Followers from './Followers';
    
    const router = createBrowserRouter([
      {
        path: '/',
        element: <App />,
        children: [
          {
            path: '',
            element: <Home />,
          },
          {
            path: 'user/:userId',
            element: <User />,
            children: [
              {
                path: 'followers',
            	 element: <Followers />,
              }
            ],
          },
        ],
      },
    ]);
    
    export default router;

    이제 Outlet 이 활성화 되어 User스크린에서 Follower스크린을 자식으로서 렌더할 수 있게 된다.
    Outlet을 사용함으로써 위치를 체크하지 않아도 되고, 라우터를 추가로 생성할 필요가 없고, 그저 자식으로 정의 하기만 되서 레이아웃을 짜기에 훨씬 간단해졌다.

7. useOutletContext

useOutletContext로 Outlet을 통해 렌더된 컴포넌트에 데이터를 전달할 수 있다. 직접 접근할 수 있는 url을 가질 수 있고 여러 자식 요소들에 Outlet으로 동일한 데이터를 전달할 수 있다.

위의 예시코드로 확인해보자

  • Outlet 컴포넌트에 context로 어떤 데이터 형식으로든 데이터를 전달함으로써 User의 모든 자식들에게 보낼 수 있다.

    // User.tsx
    import { useParams, Outlet, Link } from 'react-router-dom';
    import { users } from '../db'; 
    function User () {
     const { userId } = useParams();
      return(
        <div>
          <h1>
            {userId}id's user is named : {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;
  • 보낸 데이터를 받을 자식 컴포넌트에서 useOutletContext()를 사용하여 데이터를 받을 것이라는 것을 알려주어야 한다.

    // Followers.tsx
    import { useOutletContext } from "react-router-dom";
    
    interface IFollowersContext {
      nameOfMyUser : string;
    }
    
    function Followers () {
      const { nameOfMyUser } = useOutletContext<IFollowersContext>();
      //구조분해할당을 통해 넘겨받은 객체 데이터의 속성을 개별 변수로 담을 수 있다.
      return (
        <h1>Here are {nameOfMyUser}'s followers</h1>
      );
    }
    export default Followers;

8. useSearchParams

이 훅은 URL의 search parameter를 수정하거나 읽어내는 것에 도움을 준다. search parameter는 흔히 아는 쿼리스트링이랑 같은데 url주소에서? 다음으로 오는 주소를 뜻한다.
이는 검색하거나, 필터링하거나, 페이지네이션 할 때의 정보들을 넣을때 주로 사용된다.
useSearchParams() 훅을 사용하면 기본적으로 array를 제공한다.

[서치파라미터를읽기위한것, 서치파라미터를수정하기위한것] = useSearchParams();

간단한 예시코드를 통해 알아보자

// Home.tsx
import { users } from "../db";
import { Link, useSearchParams } from "react-router-dom";

function Home () {
  const [readSearchParams, setSearchParams] = useSearchParams();
  return (
    <div>
      <h1>Users</h1>
      <ul>
        {users.map((user) => (
          <li key={user.id}>
            <Link to={`/users/${user.id}`}>
              {user.name}
            </Link>
          </li>
        ))}
      </ul>
    </div>
  )
}

서치파라미터가 없을때 console.log(readSearchParmas)를 하면 브라우저에는 URLSearchParams {} 빈 객체가 나온다.
이는 useSearchParams를 사용할때는 URLSearchParams의 메소드를 사용해야한다는 의미이기도 하다. 자주쓰이는 메소드는 나의 다른글 목록에서 확인할 수 있다.

profile
프론트엔드 개발자

0개의 댓글