React

React Router v6 정리

오스타 2022. 4. 4. 18:51

React Router v6

 

 

React Router는 현재 리액트의 라우팅 기능을 지원하는 가장 인기있는 라이브러리입니다.

이 React Router가 지난 2021년 11월에 정식 6.0 버전이 릴리즈되었습니다. 5.0 버전에서 큰 변경사항이 없었고 이후 꽤나 오랜만에 출시된 버전이기에 라이브러리에 많은 변화가 있습니다.

그래서 v5를 사용하고 있는 프로젝트에 v6 도입을 원활하게 하기 위해 두 버전 간의 차이를 중심으로 정리해보았습니다.

 

React Router v6는 React 16.8 이상의 버전을 필요로 합니다

React Router v6는 React Hook을 많이 사용하기 때문에 버전 업그레이드를 위해 React 16.8 이상의 버전을 사용해야 합니다.

좋은 소식으로는 React Router v5가 React 15 이상과 호환됨에 따라 v5 사용시 라우터 코드를 건드리지 않고 React 업그레이드가 가능합니다.

(새롭게 릴리즈된 React 18 버전 StrictMode에서 React Router v5이 제대로 작동하지 않는 모습을 보이는 것 같습니다. v6 라우팅에는 문제가 보이지 않지만 이후 버그나 패치 과정을 지켜봐야할 것 같습니다)

Ref. "react-router does not perform with React 18 StrictMode"

 

 

<Switch> -> <Routes>

// v5
<Switch>
  <Route exact path="/" component={Home} />
  <Route path="/intro" component={Intro} />
  <Route path="/blog" component={Blog} />
</Switch>
​
// v6
<Routes>
  <Route path="/" element={<Home />} />
  <Route path="/intro" element={<Intro />} />
  <Route path="/blog" element={<Blog />} />
</Routes>

v5에서는 location이 일치하는 첫 번째 <Route>요소를 렌더링하는 <Switch> 컴포넌트를 제공합니다. 그리고 Route의 'path' prop을 정의할 때 'exact' 속성을 붙여주면 location.pathname이 정확하게 일치하는 경우에만 렌더링이 진행됩니다. v5에서 이러한 기능을 제공하는 것은 <Route>의 순서에 따라 잘못된 컴포넌트들이 렌더링될 수 있는 버그가 발생하는 것을 방지하기 위함이었습니다.

v6에서는 <Switch> 대신 <Routes> 컴포넌트를 제공하여 이 문제를 해결합니다. <Routes> 컴포넌트는 <Route> 순서와 상관없이 경로가 일치하는 컴포넌트를 렌더링합니다. exact의 사용도 필요 없습니다.

 

 

<Route> 사용법 변경

// v5
<Route path="/intro" component={Intro} />
​
<Route path="/intro">
  <Intro />
</Route>
​
<Route path="/intro" render={() => <Intro />} />
        
// v6
<Route path="/intro" element={<Intro />} />
<Route path="/intro/*" element={<Intro />} />
<Route path="*" element={<NotFound />} />

v5에서는 해당 경로에 렌더링할 컴포넌트를 지정하기 위해 component prop, render prop, child 컴포넌트로 지정하는 등의 방식이 존재했지만, v6는 element props로 변경되었습니다.

path 지정에도 차이가 있습니다. v5에서는 기본적으로 path를 포함하고 있으면 컴포넌트가 렌더링되며 경로가 정확하게 일치할 때만 레더링하기 위해서는 'exact' 속성을 부여해야 합니다. 반면 v6에서는 기본적으로 정확히 일치하는 경로의 컴포넌트만 렌더링합니다. 여러 라우팅에 매칭하기 위해서는 path에 *를 붙여줍니다. path="*"를 활용하면 not found 페이지 라우팅도 지정해줄 수 있습니다.

 

 

중첩 라우팅 구현의 변화

v6에서는 <Route> path 지정 시에 상대경로를 지정해줄 수 있게 되면서 중첩 라우팅 구현이 더 간편해졌습니다.

// v5
​
<Switch>
  <Route path="/home" component={Home} />
  <Route path="/intro" component={Intro} />
  <Redirect from="/" to="/home" />
</Switch>
// App.jsx
​
const { url } = useRouteMatch();
<>
  <h2>This is Introduction Page</h2>
  <Route path={`${url}/:index`} component={IntroDetail} />
</>
// Intro.jsx

v5에서는 중첩라우팅 구현 시 useRouteMatch()등을 활용해서 현재 url정보를 가져와 절대경로를 만들어주는 과정이 필요했습니다.

// v6
​
<Routes>
  <Route path="/home" element={<Home />} />
  <Route path="/intro/*" element={<Intro />} />
  <Route path="/" element={<Navigate replace to="/home" />} />
  <Route path="*" element={<NotFound />} />
</Routes>
// App.jsx
​
<Routes>
  <Route path=":index" element={<IntroDetail />} />
</Routes>
// Intro.jsx

반면 v6는 상대경로를 지정해줄 수 있기 때문에 별도로 url 정보를 가져와 경로를 다시 만들어 줄 필요가 없습니다. 물론 부모 컴포넌트 라우트에 *를 붙여주어 하위 라우팅을 탐색할 수 있게끔 해주어야 합니다.

 

// v6
​
<Routes>
  <Route path="/home" element={<Home />} />
  <Route path="/intro/*" element={<Intro />}>
    <Route path=":index" element={<IntroDetail />} />
  </Route>
  <Route path="/" element={<Navigate replace to="/home" />} />
  <Route path="*" element={<NotFound />} />
</Routes>
// App.jsx
​
<>
  <h2>This is Introduction Page</h2>
  <Outlet />
</>
// Intro.jsx

추가적으로 v6에서는 <Outlet> 컴포넌트를 활용해서 상위 컴포넌트 하나의 <Routes>에서 중첩라우팅을 구현해줄 수 있습니다. 라우트 코드를 한데 모아서 구성하기에는 본 방식이 더 유용하지 않을까 생각이 듭니다.

 

 

<Redirect> 사용의 변화

기존 react router v5는 <Redirect> 컴포넌트를 활용해 리디렉션을 진행했던 반면, v6에서는 <Redirect>을 없애고 되도록이면 서버측에서 리디렉션을 처리할 것을 권장하고 있습니다.

클라이언트가 아닌 서버 측에서 리디렉션을 처리하게 되면 분명한 이점들이 있습니다.

먼저 렌더링 전에 리다이렉트를 처리하기 때문에 시간을 절약하고 서버 렌더링 리소스를 낭비하지 않게 됩니다. 또 검색 엔진 크롤러는 리디렉션을 보고 페이지 색인화를 피할 수 있어 SEO 측면에서도 더 나은 구현입니다.

 

// v5
<Redirect from="/" to="/home" />
​
// v6
<Route path="/" element={<Navigate replace to="/home" />} />

물론 서버 렌더링을 사용하지 않고 리디렉션을 진행하는 방법을 v6에서도 제공하고 있습니다. 기존 v5에서는 <Switch> 내부에서 <Redirect> 컴포넌트를 사용하는 형태였는데 반해 v6에서는 <Navigate> 컴포넌트를 활용합니다.

<Navigate> 컴포넌트는 'replace' 속성 부여 시 내부적으로 history.replaceState() 메소드를 통해 구현됩니다. 'replace' 속성 없이 디폴트로 구현될 경우 history push 로직을 따릅니다.

Ref. "Redirects in React Router v6"

 

 

useHistory() -> useNavigate()

// v5
const { goBack, go } = useHistory();
<div>
  <button onClick={() => goBack()}>뒤로가기</button>
  <button onClick={() => go(-2)}>뒤로 두번 가기</button>
</div>
​
// v6
const navigate = useNavigate();
<div>
  <button onClick={() => navigate(-1)}>뒤로가기</button>
  <button onClick={() => navigate(-2)}>뒤로 두번 가기</button>
</div>

v6에서는 리액트 히스토리 변경 훅인 useHistory가 useNavigate라는 훅으로 변경되었습니다. 사용법은 위와 같이 변경되었습니다. 기존 훅에서는 여러 메소드가 존재했지만 v6에서는 navigate()메소드에 특정 숫자를 넘기면 현재 페이지에서 해당 숫자만큼 히스토리를 이동할 수 있습니다.

<button onClick={() => navigate("details")}>
  세부 소개 내용 보기
</button>

navigate() 메소드는 숫자를 전달해 히스토리를 이동할 수 있을 뿐 아니라 특정 URL string을 전달해 페이지를 이동할 수 있습니다. 상대 경로를 전달하는 것도 가능합니다.

v6에서 histroy API를 직접 사용하기보단 navigate API를 사용하는 것은 React Suspense와의 호환성을 높이기 위함입니다. 예를 들어 이전에 클릭한 링크의 로딩이 끝나지 않았는데 또다른 라우트 링크를 클릭하였을 경우, navigate API는 내부 pending 상태를 인식하고 히스토리 push 작업 대신 replace 작업을 진행하게 됩니다. 결론적으로 실제로 로드되지 않은 페이지는 히스토리에 남지 않게 되는 것입니다.

 

 

번들 사이즈 감소

[ react-router-dom 버전별 번들 사이즈 ]

위 링크에서 보는 바와 같이 버전업이 되면서 코드만 효율적으로 변경된 것이 아니라 번들 사이즈가 크게 감소했습니다.

대략 두 버전의 번들 파일을 비교했을 때 약 40%의 사이즈가 감소한 것을 확인할 수 있습니다.

 

 

Reference

"Upgrading to React Router v6"

React Router v6 공식문서

React Router v5 공식문서

 

LIST

'React' 카테고리의 다른 글

고차 컴포넌트(HOC)와 Hooks  (3) 2022.05.29
React Testing Library 튜토리얼  (0) 2022.03.28