\(@^0^@)/

[TIL] react-router v5 VS. react-router v6 본문

TIL

[TIL] react-router v5 VS. react-router v6

minjuuu 2022. 8. 28. 23:09
728x90

react-router가 v6로 업그레이드된 지 아직 일 년도 되지 않았기에,
다른 사람의 코드를 참조하려고 살펴보면 v5로 작성되어 있는 경우를 많이 봤고,
v6를 사용한다고 하지만 v5같이 쓰는 경우도 많이 볼 수 있었다.

그래서 기존의 v5에서 v6로 어떻게 바뀌었는지 차이점을 알아보고,
새로 바뀐 v6를 제대로 사용하려면 어떻게 해야 하는지 알아보고 싶어서 정리를 하였다.


npm install react-router-dom@6

yarn add react-router-dom@6

// 최신버전
yarn add react-router-dom@latest

  • Switch(v5) -> Routes(v6)
// v5
import {Switch, Route} from "react-router-dom";
import Home from "./pages/Home";

function App() {
  return (
    <Switch>
      <Route path="/">
        <Home />
      </Route>
    </Switch>
  )
}
// v6
import {Routes, Route} from "react-router-dom";
import Home from "./pages/Home";

function App() {
  return (
    <Routes>
      <Route path="/">
        <Home />
      </Route>
    </Routes>
  )
}

  • useHistory(v5) -> useNavigate(v6)
// v5
import { useHistory } from "react-router";

function Todo() {
  const history = useHistory();
  
  return (
    <>
      <button>
        onClick={() => {
            history.push("/");
          }}
      </button>
      <button>
        onClick={() => {
            history.goback();
          }}
      </button>
    </>
  )
}
// v6
import { useNavigate } from "react-router";

function Todo() {
  const navigate = useNavigate();
  
  return (
    <>
      <button>
        onClick={() => {
            navigate("/");
          }}
      </button>
      <button>
        onClick={() => {
            navigate(-1);
          }}
      </button>
    </>
  )
}

  • useNavigate(v6)를 이용하여 현재 페이지를 새 페이지로 교체 또는 푸시할 수 있다.
// v6
import { Routes, Route, Navigate } from "react-router-dom";
import Home from "./pages/Home";

function App() {
  return (
    <Routes>
      <Route path="/" element={<Navigate to="/welcome" />} />
    </Routes>
  )
}

해당 Route는 새 페이지를 푸시하도록 하는 것인데,
거기에 replace만 추가해주면 현재 페이지를 새 페이지로 교체하는 리다이렉트가 발생하는 것.

// v6
import { Routes, Route, Navigate } from "react-router-dom";
import Home from "./pages/Home";

function App() {
  return (
    <Routes>
      <Route path="/" element={<Navigate replace to="/welcome" />} />
    </Routes>
  )
}

 

아래와 같이 Navigate를 변수로 선언해주고, 두 번째 인자에 replace를 넣는 방식으로 사용할 수도 있다.
그러면  현재 경로가 새 경로로 바뀌고, 새 경로를 푸시하는 대신 redirect를 한다.

// v6
import { useNavigate } from "react-router";

function Todo() {
  const navigate = useNavigate();
  navigate('/user', {replace: true});
  
  return (
    <>
      <button> onClick={() => {navigate("/")}} </button>
    </>
  )
}


또한 path를 넘기는 대신, 숫자를 넣어주면 이전 페이지(-1) 또는 이전 페이지의 이전 페이지(-2)로 이동, 1을 넣어준다면 다시 앞으로 나아가는 등의 앞뒤 탐색 이동이 가능하다.

// v6
import { useNavigate } from "react-router";

function Todo() {
  const navigate = useNavigate();
  navigate(-1);
  
  return (
    <>
      <button> onClick={() => {navigate("/")}} </button>
    </>
  )
}

  • useRouteMatch(v5)가 사라짐. 대신, 상대 경로를 쓸 수 있게 됨.
// v5

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

function User() {
  const { username } = useRouteMatch();
  
  return (
    <>
      <div>
        <Link to={match.url}>
          {username}
        </Link>
        <Link to={`${match.url}/mypage`}> My Page </Link>
      </div>
      <div>
        <Route path={match.path}>
          <UserMain />
        </Route>
        <Route path={`${match.path}/mypage`}>
          <Mypage />
        </Route>
      </div>
    </>
  )
}

match.url과 match.path를 읽기 위해 useRouteMatch를 사용해왔다.
(현재 경로에 기반하여 Link 또는 Route를 새로 설정하기 위해)

하지만 v6부터는 상대 경로를 사용할 수 있기 때문에 match를 사용하지 않아도 됨.

// v6

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

function User() {
  const { username } = useRouteMatch();
  
  return (
    <>
      <div>
        <Link to="" >
          {username}
        </Link>
        <Link to="mypage"> My Page </Link>
      </div>
      <div>
        <Route path="">
          <UserMain />
        </Route>
        <Route path="mypage">
          <Mypage />
        </Route>
      </div>
    </>
  )
}

<Link to=""> : 공백을 넣어줄 경우, 현재 매치된 router의 경로가 사용된다.
<Link to="/mypage"> : 앞에 / 를 해줄 경우, url이 /mypage인 페이지로 이동한다.
<Link to="mypage"> : 앞에 / 이 없을 경우, 현재 route 된 경로 url의 뒷부분에 mypage를 추가하여 이동.


  • Route에 children이나 component 대신에, element 사용
// v5

function App() {
  return (
    <Routes>
      <Route path="/">
        <Home />
      </Route>
      <Route path="/user/:username">
        <User />
      </Route>
      <Route path="/user/:id">
        <Todo />
      </Route>
    </Routes>
  )
}

기존의 v5는 < Route > 안에 children 형식으로 컴포넌트를 넣었는데,

// v6

function App() {
  return (
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/user/:username" element={<User />} />
      <Route path="/user/:id" element={<Todo />} />
    </Routes>
  )
}

v6는 element 형식으로 표현하기에 가독성이 좋다.


  • Route는 Routes의 직속 자식이어야만 작동을 한다.
// v6

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

function User() {
  const { username } = useRouteMatch();
  
  return (
    <>
      <div>
        <Link to="" >
          {username}
        </Link>
        <Link to="mypage"> My Page </Link>
      </div>
      <div>
      <Routes>
        <Route path="" element={<UserMain />} />
        <Route path="mypage" element={<Mypage />} />
      </Routes>
      </div>
    </>
  )
}

따라서 Routes를 import 해서 Route를 감싸주고,
Route에 children이나 component 대신 element를 사용해야 하기에, 위와 같이 변경해야 한다.


  • Route에 exact Prop이 사라지고, 서브 경로가 필요한 경우에는 path에 * 사용.
// v5

function App() {
  return (
    <Routes>
      <Route path="/" exact element={<Home />} />
      <Route path="/user/:username" element={<User />} />
      <Route path="/todo/*" element={<Todo />} />
    </Routes>
  )
}

기존의 exact prop를 사용하지 않아도, v6에서부터는 default로 exact 속성을 모두 가지고 있다고 생각하면 된다.
그렇기에 반대로, exact가 필요하지 않을 때 * 를 사용하면 된다.

// v6

function App() {
  return (
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/user/:username" element={<User />} />
      <Route path="/todo/*" element={<Todo />} />
    </Routes>
  )
}

  • Todo 컴포넌트에 하위 Route를 추가할 경우, path 처리
// v6

function App() {
  return (
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/user/:username" element={<User />} />
      <Route path="/todo/*" element={<Todo />} />
    </Routes>
  )
}
function Todo() {
  return (
    <section>
      <h1> Todo List </h1>
      <Link to="todo/new-user">New User</Link>
      <Routes>
        <Route path="todo/new-user" element={<p><Welcome, new user!/p>} />
      </Routes>
    </section>
  )
}

url에서 todo/new-user로 들어가 로드할 경우에 p태그 안의 Welcome, new user! 가 보여야 하는데,
실행해보면 화면에서 에러는 뜨지 않지만, p태그 안의 문장이 보이질 않는다.
Route를 Routes로 잘 감쌌고, children이나 component 대신에 element 요소로 잘 적용했는데 왜 그럴까?

정답은 path에 있다. 현재 우리는 Todo 컴포넌트 안에 있기 때문에, todo를 또 적을 필요가 없음.
이렇게 될 경우 todo/todo/new-user 이런 식으로 path가 처리되어 있기 때문에 우리가 원하는 todo/new-user의 p태그 속 문장이 보이질 않는 것이다.

function Todo() {
  return (
    <section>
      <h1> Todo List </h1>
      <Link to="new-user">New User</Link>
      <Routes>
        <Route path="new-user" element={<p><Welcome, new user!/p>} />
      </Routes>
    </section>
  )
}

따라서, 위와 같이 바꿔준다면 제대로 작동할 것이며, Link 또한 마찬가지.

// v6

function App() {
  return (
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/user/:username" element={<User />}>
        <Route path="new-user" element={<p><Welcome, new user!/p>} />
      </Route>
      <Route path="/todo/*" element={<Todo />} />
    </Routes>
  )
}

하지만 이 또한 App 컴포넌트에 구성한다면 모든 경로 정의가 한 곳에 있어서, 지원되는 모든 경로를 보고 프로젝트를 작업하는 것이 훨씬 쉬울 것 같다는 생각이다.

function Todo() {
  return (
    <section>
      <h1> Todo List </h1>
      <Link to="new-user">New User</Link>
      <Outlet/>
    </section>
  )
}

또한 Todo컴포넌트에서 Outlet을 사용하여 어느 부분에  중첩 Route가 있는지 알려줘야 한다.


  • 서브 Route를 구현하는 또 다른 방법 : Outlet
// v5

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

function User() {
  const { username } = useRouteMatch();
  
  return (
    <>
      <div>
        <Link to="" >
          {username}
        </Link>
        <Link to="mypage"> My Page </Link>
      </div>
    </>
  )
}
// 지운 코드
<Routes>
    <Route path="" element={<UserMain />} />
    <Route path="mypage" element={<Mypage />} />
</Routes>

기존의 Routes안의 Route들을 잘라내고, Routes 또한 지워준다.

// v6

function App() {
  return (
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/user/:username/*" element={<User />} >
        <Route path="" element={<UserMain />} />
    	<Route path="mypage" element={<Mypage />} />
      </Route>
      <Route path="/user/:id" element={<Todo />} />
      <Route path="/optional/:value?" element={<Optional />} />
      <Route path="/optional" element={<Optional />} />
    </Routes>
  )
}

잘라낸  Route 코드를 App 컴포넌트에 복붙 하고

// v5

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

function User() {
  const { username } = useRouteMatch();
  
  return (
    <>
      <div>
        <Link to="" >
          {username}
        </Link>
        <Link to="mypage"> My Page </Link>
      </div>
      <Outlet />
    </>
  )
}

기존의 Routes, Route가 있던 User 컴포넌트에 Outlet을 추가한다면,  서브 Route가 구현된다.
즉, nested Route를 사용할 계획이라면 무조건 상위 경로인 Route 또는 최상위인 App 컴포넌트에서 사용하는 것이 좋다.
이렇게 구현해줄 경우 App 컴포넌트에서 Route들이 더욱 가시적으로 확인할 수 있다는 장점이 있다.


  • Optional URL 파라미터 기능 사라지고, 필요한 경우 Route를 2개 만들어야 한다.
// v5

function App() {
  return (
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/user/:username/*" element={<User />} />
      <Route path="/user/:id" element={<Todo />} />
      <Route path="/optional/:value?" element={<Optional />} />
    </Routes>
  )
}
// v6

function App() {
  return (
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/user/:username/*" element={<User />} />
      <Route path="/user/:id" element={<Todo />} />
      <Route path="/optional/:value?" element={<Optional />} />
      <Route path="/optional" element={<Optional />} />
    </Routes>
  )
}

  • NavLink에 activeStyle, activeClassName이 사라짐.

NavLink는 현재 페이지가 활성화되어있으면, link에 또 다른 스타일을 적용하는 것.
ex) 현재 user페이지를 보고 있다면, user화면에 특별한 style을 적용하는 컴포넌트

// v5

<>
  <NavLink
    to="/logined"
    style={{ color: 'black' }}
    activeStyle={{ color: 'red' }}
  >
    Logined
  </NavLink>
</>

<>
  <NavLink
    to="/logined"
    className="nav-link"
    activeClassName="activated"
  >
    Logined
  </NavLink>
</>
// v6

<>
  <NavLink
    to="/logined"
    style={({ isActive }) => ({ color: isActive ? 'red' : 'black' })}
  >
    Logined
  </NavLink>
</>

<>
  <NavLink
    to="/logined"
    className={({ isActive }) => "nav-link" + (isActive ? " activated" : "" )}
  >
    Logined
  </NavLink>
</>

v6에서는 함수와 삼항 연산자를 통해서 보다 간결하게 사용.


https://reactrouter.com/en/v6.3.0/upgrading/reach

 

Migrating from @reach/router

Migrating from @reach/router

reactrouter.com

https://www.youtube.com/watch?v=CHHXeHVK-8U&t=1s

https://www.youtube.com/watch?v=UjHT_NKR_gU

https://www.youtube.com/watch?v=zEQiNFAwDGo

 

728x90