[React] react-router-dom을 vue-router처럼 쓰기

leteu

·

2023. 3. 4. 21:17

오랜만에 글을 써본다.

재미로 쓴 flutter가 전체 조회수의 약 80~90% 정도를 차지해서 현타가 왔다.

회사에서 리액트를 쓸 일이 생겨 알아보던 중 라우팅 코드가 굉장히 더러운 거 같은데

vue-router 쓰던 입장으로썬 이해가 안 가서 비슷하게 한번 만들어봤다.


 

#1 react-router-dom 이란

 

https://reactrouter.com/

 

Declarative routing for React apps at any scale | React Router

Version 6 of React Router is here! React Router v6 takes the best features from v3, v5, and its sister project, Reach Router, in our smallest and most powerful package yet.

reactrouter.com

 

리액트에서 vue-router 같이 라우팅을 쉽게 해주는 라이브러리다.

이름만 비슷하지 사용법은 vue-router가 훨씬 낫다.

( 개인적인 의견이고 사용한 지 하루밖에 안 지났다 )

 

위 사이트의 Docs 보고 잘 따르기만 하면 쉽게 할 수는 있다.

 

일단 vue-router처럼 만들어야겠다 마음먹은 이유는 아래 코드처럼 route를 정의하기 때문이다.

 

import ReactDOM from "react-dom/client";
import {
  BrowserRouter,
  Routes,
  Route,
} from "react-router-dom";
// import your route components too

const root = ReactDOM.createRoot(
  document.getElementById("root")
);
root.render(
  <BrowserRouter>
    <Routes>
      <Route path="/" element={<App />}>
        <Route index element={<Home />} />
        <Route path="teams" element={<Teams />}>
          <Route path=":teamId" element={<Team />} />
          <Route path="new" element={<NewTeamForm />} />
          <Route index element={<LeagueStandings />} />
        </Route>
      </Route>
    </Routes>
  </BrowserRouter>
);

 

라우팅 정보만 확실하게 눈에 들어온다기 보단 수많은 <Route />가 나를 반겨준다.

정말 싫다.

 

#2  시작하기

 

vue-router는 보통 router 폴더 안에 index.ts/js랑 routes.ts/js 넣어놓고 꺼내 쓴다.

 

-- app
		|-- src
				|-- router
						|-- index.ts
						`-- routes.ts

 

이 포스트는 TypeScript로 작성되었지만 JavaScript로 하고 싶다면 그냥 type 정의된 부분만 지워서 쓰면 된다.

위 코드 구조대로 scr 폴더에 router 폴더를 만들고 그 안에 index와 routes 파일을 만들어 주자.

 

#3 router/routes.ts

 

vue-router에 routes.ts를 보면

import 컴포넌트 from '../pages/컴포넌트.vue'

const routes = [
	{
		name: 'Component',
		path: '/component',
		component: 컴포넌트,
		meta: { ... },
		children: [{...}]
	}
]

export default routes;

이런 느낌이다.

 

비슷하게 만들고 meta는 이번 글에서는 다루지 않을 것이다.

아직 react-router-dom에 모르는 기능이 있기 때문에 우선 넘어가려 한다.

 

한번 만들어 보면

export interface RouteInterface {
	name: string;
	path: string;
	component: ReactNode;
	index?: boolean;
	children?: RouteInterface[];
}

import { ReactNode } from "react";
import { MainLayout } from "../layouts";
import { Home, About, NotFound, AboutView } from "../pages";

const routes: RouteInterface[] = [
	{
		name: 'main', path: '/', component: MainLayout(),
		children: [
			{ name: "home", path: "home", component: Home() },
			{
                name: "about",
                path: "about",
                component: About(),
				children: [
					{
                        name: "aboutView",
                        path: ":id",
                        component: AboutView(),
					},
				],
			},
		]
	},

	{ name: "404", path: "*", component: NotFound() },
];

export default routes;

이런 코드가 나온다. 저장해준다.

 

예제는 예제일 뿐이니 Interface에 맞게 본인 환경에 구성된 파일들을 사용해 만들어 준다.

없으면 그냥 똑같이 같은 경로에 만들어 주자

 

대충 설명하자면 MainLayout에서는 공통된 디자인을 호출하고

react-router-dom의 <Outlet />을 사용하여 vue-router의 <router-view />처럼 사용해 보려고 한다.

 

그 밑으로는 그냥 아무거나 적어둔 tsx 파일들이고 폴더의 index.ts에서 export 해두었다.

 

마지막 줄의 404는 위 라우터 어디에도 속하지 않을 경우 보내버릴 "404 Not Fonund" 페이지이다.

 

#4 router/index.ts

 

vue-router에 index.ts는 보통 vue-router 라이브러리 가져와서 라우터 하나 새로 만들고 안에 기능 가져다 쓰고 meta 잡아서 기능 넣고 하지만 이번엔 다 넘긴다. 쉽게 쉽게 가고 필요할 때 하나하나 붙여보려고 한다.

 

import { Component, createElement, ReactNode } from "react";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import routes, { RouteInterface } from "./routes";

class Router extends Component {
  render() {
    return createElement(
      BrowserRouter,
      {},
      createElement(
        Routes,
        {},
        routes.map((item, index) => {
          return createElement(
            Route,
            {
              path: item.path,
              element: item.component,
              index: item.index,
              key: `${item.name}-${item.index}`
            },
            item.children ? this.getChildren({ children: item.children, parentKey: `${item.name}-${index}` }) : void 0
          );
        })
      )
    )
  };

  getChildren({ children, parentKey }: { children: RouteInterface[]; parentKey: string; }): ReactNode[] {
    return children.map((item, index) => {
      return createElement(
        Route,
        {
          path: item.path,
          element: item.component,
          index: item.index,
          key: `${parentKey}-${item.name}-${index}`
        },
        item.children ? this.getChildren({ children: item.children, parentKey: `${parentKey}-${item.name}-${index}` }) : void 0
      );
    });
  };
};

export default Router;

 

이걸 직접 짜고 있을 바엔 그냥 아까 그 더러운 코드를 쓰는 게 좋을 수도 있을 거 같다.

사용하고 싶은 사람만 복사해서 사용하면 된다.

 

ts 파일이기 때문에 class로 컴포넌트를 만들어주어야 한다.

react에서 Component를 가져와 클래스를 확장시켜주도록 하자.

 

class로 컴포넌트를 만들면 render 함수로 만들어야 하나 보다 (리액트 쓴 지 하루밖에 안돼서 잘 모른다)

하라는 대로 해주고 children 같은 경우엔 여러 번 있을 수 있으니 재귀 함수로 만들어 둔다.

 

#5 App.tsx

이제 마지막이다. App.tsx에서 호출만 해주면 끝이다.

처음에 봤던 코드보다 훨씬 깔끔하다.

routes.ts에서 오브젝트만 추가해주면 자동으로 라우팅이 추가될 것이다.

 

import './App.css'
import Router from './router'

function App() {
  return (
    <div className="App">
      <Router />
    </div>
  );
};

export default App

 

 

#6 끝내며...

 

아직 react-router-dom을 완벽하게 이해하지 못했고 vue-router의 기능을 전부 구현한건 아니지만 라우팅 자체는 깔끔하게 나온거 같아 만족스럽다. 좀더 연구해보고 살을 붙이거나 계속 이대로 사용해야겠다.