react-router-dom v6 마이그레이션

크림카카오·2022년 6월 27일
0

기존에 운영하던 B2B향 서비스와 CMS를 react-router-dom v6로 마이그레이션하면서 작성한 내용이다. 영향도가 적은 프로젝트부터 순차적으로 적용하고, 이후 B2C향 서비스에도 적용할 예정이다.

목차

  • 개요
  • 전제 조건
  • v5와 달라진 점
  • 이슈 및 해결방안

개요

전제 조건

  • React Hook을 많이 사용하므로 React v.16.8 이상 프로젝트에 적용해야 한다.

v5와 달라진 점

주요 차이점은 아래와 같다.

1. Switch 대신 Routes를 사용해야 한다.

Switch 대신 Routes를 사용해야 한다.

// v5 
const App = () => (
  <Switch>
    <Route ... />
  </Switch>
)

// v6
const App = () => (
  <Routes>
    <Route ... />
  </Routes>
)

2. exact가 사라졌다.

v5까지는 exact 속성을 통해 path를 판단했으나, v6부터는 exact=true가 기본 값이다.

// v5 
const App = () => (
  <Switch>
    <Route path="/" exact ... /> 
  </Switch>
)

// v6
const App = () => (
  <Routes>
    <Route path="/" ... />
  </Routes>
)

3. component 대신 element를 사용해야 하고, render 속성은 사용할 수 없다.

v5에서 Route를 사용해서 경로를 표시할 수도 있지만, 배열로 작성한 route 정보를 호출하여 다이나믹하게 표기했다. 이때 render 속성에서 match 등을 꺼내서 요리조리 사용할 일이 종종 있었다. render 속성이 없어지면서 각 컴포넌트 내에서 useMatch() hook을 호출해서 사용해야 한다. (참고로 v5에서 사용하던 useRouterMatch()는 v6에서 없어졌다.)

// v5 
import routes from "./routes";

const App = () => (
  <Switch>
    <Route path="/" component={<Main />} exact /> 
    {
      routes.map(route => {
        <Route render={({match}) => <route.component match={match} />} key={route.key} path={route.path} > 
      })
    }
  </Switch>
)

4. route의 Hierarchy를 정의하는 방식의 변화

v6에서는 컴포넌트 방식, hook 방식으로 route의 Hierarchy를 정의한다. 개인적으로 가장 편리하다고 생각하는 방식이다.

컴포넌트 방식

const App = () => (
  <Routes>
    <Route path="/" element={<Main />}>
      <Route index element={<AboutSite />}> // localhost:8080/
      <Route path="notices" element={<Notices />}> // localhost:8080/notices   
        <Route path="privacy" element={<Privacy />}> // localhost:8080/notices/privacy 
        <Route path="terms"  element={<Terms />}> //localhost:8080/notices/terms 
      </Route>  
    </Route>
    <Route path="*" element={<PageNotFound />}>
  </Routes>
)

Hook 방식

// routes.js
const Routers = () => {
  const routes = useRoute(() => [
    { 
      path: '/', 
      element: <Main />,
      children: [
        { index: true, element: <AboutSite /> },
        { path: 'notices', 
          element: <Notices />,
          children: [
            { path: 'privacy', element: <Privacy /> },
            { path: 'terms', element: <Terms /> }
          ]
        },
      ],
    },
    { path: '*', element: <PageNotFound /> }                    
  ])
}

// App.js 
import Routers from './routes';

const App = () => (
  ...
    <Routers />
  ...
);

5. useHistory() 대신 useNavigate()을 사용한다.

useHistory()는 사라지고, useNavigate()을 통해 history stack을 오갈 수 있다.

// v5
const history = useHistory(); 
history.push('/notices');
// v6
const navigate = useNavigate(); 
navigate('/notices');   

// v5
history.replace('/notices');
// v6
navigate('/notices', {replace: true});  

// v5 
history.push('/notices', {state: {isLogin: true});
navigate('/notices', {state: {isLogin: true});                          

이슈 및 해결방안

이슈 1. 마이그레이션이라고 하기엔, 변경해야 할 코드가 너무 많다.

v5 기반의 route 경로를 수정하는 것은 어렵지 않았다. 다만, 거의 대부분의 페이지에서 사용하고 있는 useHistory()가 문제가 되었다. useHistory()에서 location, history 객체를 꺼내와서 활용하고 있는 코드가 너무 많았던 것이다. 상용 서비스의 경우, 아주 작은 변화라도 검증을 거쳐야만 release 가능하다. 이런 작은 변화 티끌이 태산이 되는 순간 부채감이 생겼다.

해결방안 1. react-router-dom-v5-compat 활용

이 문제를 해결하기 위한 라이브러리가 있다.
react-router-dom-v5-compat

웹앱에서 v5, v6를 동시에 사용할 수 있게 해주어서, 점진적으로 마이그레이션할 수 있도록 돕는다. 관리자용 CMS의 경우, react-router-dom-v5-compat를 활용하여 마이그레이션 중이다. 마이그레이션이 완료되면 해당 라이브러리를 제거하면 된다.

profile
내가 기억하려고 남기는 글들

0개의 댓글