
더욱 더 강력해진 React Router
React Router v6.14가 업데이트되었다는 소식을 전해듣고, 이번 해커톤에서 v6.14를 적용시켜 SPA를 구현하였습니다.
이번 v6.14를 업데이트를 통해 React Router는 그저 라우팅 기능만을 하는 단순한 라이브러리가 아닌, View와 Logic을 분리할 수 있게 해주는 아주 강력한 도구가 되었다는 것을 깨닫게 되었습니다. 오늘 저는, 제가 프로젝트 진행 간에 알게되었던 것들에 대한 이야기를 해볼까 합니다.
React Router는 React를 사용하는 개발자라면 대부분이 아실테고, 거의 모든 프로젝트에서 사용하고 있을 거라고 생각합니다.
저는 거의 모든 프로젝트에서 React Router를 이용하여 SPA를 구현하였습니다. React Router가 제공해주는 강력한 기능들이 많이 존재하지만, React Router에 대한 아쉬움을 한가지 가지고 있었습니다. 바로 Next.js처럼 페이지 렌더링시 데이터를 받아와주는 기능이 없다는 점이였죠.
실제로, Next.js에는 getServerSideProps 함수를 통해 컴포넌트에 진입하기 전, 데이터를 외부에서 받아와 컴포넌트로 전달해주는 함수가 있습니다. 이 함수를 통해 데이터를 받아오는 Logic과 View를 쉽게 분리할 수 있었습니다. Next.js 코드 예시

하지만, React에서 React Router는 Routing 기능만을 지원했기 때문에 TodoList 페이지로 이동했을 때 외부에서 API를 통해 데이터를 받아오는 작업들을 따로 해주어야 했습니다. 즉, 다음과 같은 코드가 작성되었어야했습니다.
import React, { useEffect, useState } from "react";
const TodoList = () => {
const [todoList, setTodoList] = useState([]);
useEffect(() => {
// fetch todoList...
// reRendering
}, []);
return <ol>{/* TodoList Rendering */}</ol>;
};
export default TodoList;
React의 지향점은 선언형 프로그래밍의 개념을 강조하는 라이브러리임에도 불구하고, 중간 중간 복잡한 로직들이 있는 것을 확인할 수 있습니다. 만약, TodoList Item들에 대한 로직들을 추가한다면 더욱 컴포넌트가 복잡해지게 되겠죠.
하지만, 누군가는 이렇게 말할 것입니다. Presentational and Container components 패턴을 사용하여 컴포넌트를 분리하면, Logic과 View를 쉽게 분리할 수 있지 않나요?
물론입니다. Presentational and Container components 패턴은 과거에 자주 언급되고 활용되었던 패턴으로 아직까지도 사용하고 있는 사람도 있을 것입니다. 하지만, 저는 위 패턴을 통해 컴포넌트들을 분리하고 개발을 하다보니 어떠한 필요성 없이 이 패턴이 강제되고 있음을 느끼게 되었고, 불필요하게 컴포넌트가 많아지는 것을 확인할 수 있었습니다. 마치 아래와 같이 말이죠
src
├ components
├ TodoList.js
├ TodoListContainer.js
├ TodoItem.js
├ TodoItemContainer.js
├ Home.js
└ HomeContainer.js
└ App.js
이러한 점들때문에, 저는 항상 React Router가 너무 아쉬웠습니다. React Router가 Next.js처럼 페이지가 렌더링될 때 데이터를 받아와서 컴포넌트로 전달해주는, Container의 기능을 담당해주면 어떨까라는 생각을 하고 있었기 때문이였죠.
하지만 이번 v6.14 업데이트 이후, React Router는 굉장히 강력해졌으며, 기존에 갖고 있었던 아쉬움을 한방에 해소시켜주었습니다.
그럼, 기존의 React Router와 달리 어떻게 강력해졌는지 알아보겠습니다.
기존에 data APIs들을 사용할 수 없었던 Router들과 달리 v6.14에서는 새로운 data APIs들을 지원하는 새로운 라우터들이 도입되었는데요, 이번 해커톤에서 사용한 라우터인 createBrowserRouter에 대해 알아보겠습니다.
createBrowserRouter 라우터가 도입되고, 기존에 사용하던 방식과 어떻게 달라졌는지 알아보겠습니다.
기존에는 SPA를 구현하기 위해 BrowserRouter, Routes, Route 모듈들을 사용하여 다음과 같이 구현하여야 했습니다.
import React from "react";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Home from "./Home.js";
import TodoList from "./TodoList.js";
const App = () => {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/todoList" element={<TodoList />} />
</Routes>
</BrowserRouter>
);
};
export default App;
SPA를 구현하기 위해서 많은 module들을 import 해야만 했었고, 이렇게 import된 모듈들은 단순한 라우팅 기능만을 위해 사용되었었습니다.
하지만, 새롭게 추가된 createBrowserRouter는 라우팅 방식을 간소화시키면서 더욱 더 강력해졌습니다.
import React from "react";
import { RouterProvider, createBrowserRouter } from "react-router-dom";
import Home from "./Home.js";
import TodoList from "./TodoList.js";
const router = createBrowserRouter([
{
path: "/",
element: <Home />,
children: [
{
path: "todoList",
element: <TodoList />,
},
],
},
]);
const App = () => {
return <RouterProvider router={router} />;
};
export default App;
RouterProvider와 createBrowserRouter 모듈을 import해서 SPA를 구현할 수 있게 되면서 더욱 쉽게 구현할 수 있게 되었을 뿐만 아니라, 이전에 언급하였던 문제들도 위 라우터를 통해 쉽게 구현할 수 있게 되었는데요, 다음 loader 기능을 소개하면서 알아보겠습니다.
제가 이전에 언급했던 문제는 새로운 data APIs에서 loader를 통해 해소할 수 있었습니다.

Loader 기능이 추가되면서 위 그림처럼, 우리는 TodoList Page를 그리기 이전에 먼저 데이터를 받아올 수 있게 되었습니다. 코드는 복잡하냐구요? 전혀 복잡하지 않습니다!
const router = createBrowserRouter([
{
path: "/",
element: <Home />,
children: [
{
path: "todoList",
element: <TodoList />,
loader: todoListLoader,
},
],
},
]);
import React from "react";
import { useLoaderData } from "react-router-dom";
const TodoList = () => {
const { todoList } = useLoaderData();
return <div>{/* todoList Rendering */}</div>;
};
export default TodoList;
export async function loader() {
// fetch todoList...
// return todoList
}
어떤가요?? v6.14로 업데이트되기 전 코드와 비교해서 코드가 굉장히 간결해졌으며, View와 Logic이 깔끔하게 분리되지 않았나요? 실제로 저는 이번 프로젝트에서 이 Loader API를 이용하여 외부 데이터를 받아오는 부분을 깔끔하게 작성할 수 있었습니다.
그렇다면 외부로 데이터를 전송하는 것은 어떻게 할 수 있을까요? 다음으로는 Action API에 대해 알아보겠습니다.

Action은 외부로 데이터를 전송하는 행위를 의미하는데, 기존에 우리는 Action을 만들기 위해 다음과 같은 코드를 작성했습니다.
import React, { useState } from "react";
const TodoForm = () => {
const [todo, setTodo] = useState("");
const onAddTodo = async () => {
// 에러 체크 후
// 외부로 데이터를 전송
};
return (
<form onSubmit={onAddTodo}>
<input
type="text"
value={todo}
onChange={(e) => setTodo(e.target.value)}
/>
<button>Submit</button>
</form>
);
};
export default TodoForm;
위 코드는 짧기 때문에, 가독성에는 크게 문제가 없어보입니다. 하지만, 회원가입 양식이라면 모든 상태값을 저장해줘야 할 것이며, 검증도 해야할 것입니다. 그렇게 되면 View의 양이 많아지게 될 것인데, Logic이 같이 있는 것은 코드의 가독성을 크게 떨어트리는 요인이 되겠죠.
다행히도, 우리는 React Router의 Action을 이용해서 제출 Logic을 분리할 수 있습니다. 사용하는 방법은 크게 어렵지 않습니다.
const router = createBrowserRouter([
{
path: "/",
element: <Home />,
children: [
{
path: "todoList",
element: <TodoList />,
loader: todoListLoader,
},
{
path: "add",
element: <TodoForm />,
action: addTodoAction,
},
],
},
]);
import React from "react";
import { Form } from "react-router-dom";
const TodoForm = () => {
return (
<Form>
<input type="text" />
<button>Submit</button>
</Form>
);
};
export default TodoForm;
export function action() {
// 에러 체크 후
// 외부로 데이터를 전송
}
Action을 이용하여 View와 Logic을 분리하였는데, 어떤가요 코드가 우아하지 않은가요?? 아직까지 리액트에서의 디자인 패턴이 정형되지 않은 저에게있어 위 패턴은 굉장히 매력적으로 느껴졌습니다.
Loader와 Action의 기능들 외에도 많은 기능들이 추가되었는데요.
loader와 action간에 Exception이 발생했을 때 렌더링해주는 errorElement, 코드 스플리팅을 위한 lazy 등 새로운 기능들과, 기존의 기능을 향상시켜주는 수 많은 기능이 포함되어 있어 아래 공식홈페이지를 참고하면 좋을 것 같습니다.
https://reactrouter.com/en/main
프로젝트에서 회원가입 양식을 제작하고 있었던 저는, 사용자의 UX를 향상시켜주기 위하여 페이지를 이탈할 시에 경고창을 표시하는 Prompt를 제작하려고 했습니다.
React Router에서 Prompt를 제공해주는 것을 알고 적용하려고 했는데 적용이 되지 않아 확인을 해보았더니, React Router 문서에서 기능이 완전하지 않다고 생각했는지 v6의 릴리즈 버전에서는 Prompt를 추가하지 않았다고 나와있는 것을 확인할 수 있었습니다.
Prompt가 있었더라면,, 좋았을텐데,,,
하지만 Prompt가 삭제된 것에 다른 개발자들이 말이 많았는지 v6에서 prompt를 구현하는 코드를 작성한 github gist가 있어, 위 깃허브를 참고하여 Prompt를 만들 수 있었습니다.
추후 업데이트에서 Prompt가 추가되겠죠?
업데이트된 React Router를 적용하여 프로젝트를 진행하였습니다. 처음에는 새로운 기능도 많고, 향상된 기능도 많아서 업데이트된 버전을 사용하는 것이 무조건적으로 좋다고 생각하였었습니다.
하지만 프로젝트를 진행하면서 생각보다 새로운 버전에 대한 에러가 많았으며, 이를 해결하기 위한 자료가 아직 많이 부족하구나를 알게되었습니다.
이 과정에서 무조건적으로 최신의, 유명한 라이브러리를 사용할 것이 아닌 자신이 진행하는 프로젝트의 상황에 맞춰 라이브러리를 활용하여야 한다는 중요한 것을 깨닫게 되었습니다.
https://velog.io/@godori/banner-maker - 배너 사진 참고
혹시 잘못된 내용이 있거나, 오타가 있다면 댓글로 작성해주시면 감사하겠습니다!! 😸