해커톤 프로젝트가 끝나고 미뤄두었던 주식 사이트 작업을 시작하게 되는데...
React Vite로 시작하니까 겁나 빨랐다. 역시 비트~~!!
전에 했던 프로젝트는 next.js로 작업했기 때문에 라우팅 처리가 매우x100 편했다.
그냥 pages 폴더 안에서 파일을 만들면 자동으로 라우팅이 되니까..!
하지만 react로 돌아간 나는 라우팅을 react-router-dom을 활용해서 직접 구현해야 했고.. 이것이 매우 귀찮았던 나는 구글링을 했다.
React Vite에서 파일 베이스 라우팅
을 구현하는 게시물을 찾았다!! 👏👏
|-- pages/
|-- dashboard/
|--$id.tsx
|-- analytics.tsx
|-- index.tsx
|-- about.tsx
|-- index.tsx
최종 라우팅은 다음과 같다.
''
, '/'
'/dashboard'
'/dashboard/analytics'
'/dashboard/abc'
,'/dashboard/123'
처음에 pages폴더에 index.tsx파일을 안 만들어서 오류가 났었다.😭
Vite는 import.meta.glob 함수를 이용해 여러 모듈을 한 번에 가져올 수 있는 기능을 지원하고 있다!
// @/src/App.tsx
const pages: Pages = import.meta.glob("./pages/**/*.tsx", { eager: true });
위 코드에서는 pages/
폴더에 있는 모든 모듈을 로드하고 있다.
// @/src/App.tsx
const pages: Pages = import.meta.glob("./pages/**/*.tsx", { eager: true });
const routes: IRoute[] = [];
for (const path of Object.keys(pages)) {
const fileName = path.match(/\.\/pages\/(.*)\.tsx$/)?.[1];
if (!fileName) {
continue;
}
const normalizedPathName = fileName.includes("$")
? fileName.replace("$", ":")
: fileName.replace(/\/index/, "");
routes.push({
path: fileName === "index" ? "/" : `/${normalizedPathName.toLowerCase()}`,
Element: pages[path].default,
loader: pages[path]?.loader as LoaderFunction | undefined,
action: pages[path]?.action as ActionFunction | undefined,
ErrorBoundary: pages[path]?.ErrorBoundary,
});
}
// ...
가져온 모듈의 파일명 ex)index
만 정규표현식으로 가져와서, $
가 포함되어있으면 :
로 바꿔서 동적라우팅 처리를 하고, index
인 경우 빈 경로''
로 처리를 한다.
그리고 routes
배열에
path
- 등록하려는 경로Element
- 경로에 할당하려는 React 컴포넌트loader
- 데이터 가져오기 기능 (선택)action
- form data 제출 기능 (선택) ErrorBoundary
- path 오류가 났을 때 처리하는 React 컴포넌트 (선택)을 넣는다.
// @/src/App.tsx
...
const router = createBrowserRouter(
routes.map(({ Element, ErrorBoundary, ...rest }) => ({
...rest,
element: <Element />,
...(ErrorBoundary && { errorElement: <ErrorBoundary /> }),
}))
);
const App = () => {
return <RouterProvider router={router} />;
};
export default App;
마지막으로 routes
배열을 map
으로 반복하여 RouterProvider
에 할당한다.
// @/src/App.tsx
import {
createBrowserRouter,
RouterProvider,
LoaderFunction,
ActionFunction,
} from "react-router-dom";
interface RouteCommon {
loader?: LoaderFunction;
action?: ActionFunction;
ErrorBoundary?: React.ComponentType<any>;
}
interface IRoute extends RouteCommon {
path: string;
Element: React.ComponentType<any>;
}
interface Pages {
[key: string]: {
default: React.ComponentType<any>;
} & RouteCommon;
}
const pages: Pages = import.meta.glob("./pages/**/*.tsx", { eager: true });
const routes: IRoute[] = [];
for (const path of Object.keys(pages)) {
const fileName = path.match(/\.\/pages\/(.*)\.tsx$/)?.[1];
if (!fileName) {
continue;
}
const normalizedPathName = fileName.includes("$")
? fileName.replace("$", ":")
: fileName.replace(/\/index/, "");
routes.push({
path: fileName === "index" ? "/" : `/${normalizedPathName.toLowerCase()}`,
Element: pages[path].default,
loader: pages[path]?.loader as LoaderFunction | undefined,
action: pages[path]?.action as ActionFunction | undefined,
ErrorBoundary: pages[path]?.ErrorBoundary,
});
}
const router = createBrowserRouter(
routes.map(({ Element, ErrorBoundary, ...rest }) => ({
...rest,
element: <Element />,
...(ErrorBoundary && { errorElement: <ErrorBoundary /> }),
}))
);
const App = () => {
return <RouterProvider router={router} />;
};
export default App;
라우팅이 잘 동작한다!
참고
https://dev.to/franciscomendes10866/file-based-routing-using-vite-and-react-router-3fdo