기존에 운영하던 B2B향 서비스와 CMS를 react-router-dom v6로 마이그레이션하면서 작성한 내용이다. 영향도가 적은 프로젝트부터 순차적으로 적용하고, 이후 B2C향 서비스에도 적용할 예정이다.
주요 차이점은 아래와 같다.
Switch 대신 Routes를 사용해야 한다.
// v5
const App = () => (
<Switch>
<Route ... />
</Switch>
)
// v6
const App = () => (
<Routes>
<Route ... />
</Routes>
)
v5까지는 exact 속성을 통해 path를 판단했으나, v6부터는 exact=true
가 기본 값이다.
// v5
const App = () => (
<Switch>
<Route path="/" exact ... />
</Switch>
)
// v6
const App = () => (
<Routes>
<Route path="/" ... />
</Routes>
)
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>
)
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>
)
// 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 />
...
);
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});
v5 기반의 route 경로를 수정하는 것은 어렵지 않았다. 다만, 거의 대부분의 페이지에서 사용하고 있는 useHistory()
가 문제가 되었다. useHistory()
에서 location, history 객체를 꺼내와서 활용하고 있는 코드가 너무 많았던 것이다. 상용 서비스의 경우, 아주 작은 변화라도 검증을 거쳐야만 release 가능하다. 이런 작은 변화 티끌이 태산이 되는 순간 부채감이 생겼다.
이 문제를 해결하기 위한 라이브러리가 있다.
react-router-dom-v5-compat
웹앱에서 v5, v6를 동시에 사용할 수 있게 해주어서, 점진적으로 마이그레이션할 수 있도록 돕는다. 관리자용 CMS의 경우, react-router-dom-v5-compat를 활용하여 마이그레이션 중이다. 마이그레이션이 완료되면 해당 라이브러리를 제거하면 된다.