Single Page Application이라는 용어 그대로 SPA는 하나의 페이지를 가지고 있지만 한 종류의 화면만 사용하는 것은 아닙니다. 화면이 변경되기 위해선 주소 또한 달라져야 할 필요가 있는데 이렇게 다른 주소에 따라 다른 화면을 보여주는 과정을 '경로에 따라 변경한다'라는 의미로 라우팅(Routing)이라고 합니다.
React는 View에 중점을 둔 프런트엔드 라이브러리이기 때문에 React 자체만으로는 라우팅 기능을 사용할 수 없다. 별도의 라이브러리를 설치해야지만 라우팅 기능을 사용할 수 있는데 통상적으로 React Router라는 라이브러리가 가장 많이 사용됩니다. React Router를 통한 SPA 내 화면 전환 및 주소 값 변경은 서버가 아니라 클라이언트 측에서 일어나기 때문에 Client-Side Routing이라고 합니다.
리액트를 사용한다면, React Router의 학습은 불가피한데, 기존 방식과 달라지는 v6.4에서는 성능면에서나, 기능면에서나 많은 업그레이드가 이루어졌기 때문에, 공식문서에서 소개하는, 핵심적인 기능과, 예제들을 가져와 작성하였습니다.
RouterProvider설정
createBrowserRouter함수를 통해 경로와 element가 담긴 object 배열을 argument로 받아서 RouterProvider를 생성합니다. Nested
import React from "react";
import { createRoot } from "react-dom/client";
import {
createBrowserRouter,
RouterProvider,
Route,
Link,
} from "react-router-dom";
const router = createBrowserRouter([
{
path: "/",
element: (
<div>
<h1>Hello World</h1>
<Link to="about">About Us</Link>
</div>
),
},
{
path: "about",
element: <div>About</div>,
},
]);
createRoot(document.getElementById("root")).render(
<RouterProvider router={router} />
);
Routes
React Router에서 nested layout을 생성하기 위해 아래 규칙을 따라야 합니다. 첫 번째 방식은 JSX를 활용한 방식으로 Route 컴포넌트를 불러와 path와 element를 작성해주는 방식이고, 두 번째 방식은 위에 언급한 object를 작성하는 방식 입니다.
// Configure nested routes with JSX
createBrowserRouter(
createRoutesFromElements(
<Route path="/" element={<Root />}>
<Route path="contact" element={<Contact />} />
<Route
path="dashboard"
element={<Dashboard />}
loader={({ request }) =>
fetch("/api/dashboard.json", {
signal: request.signal,
})
}
/>
<Route element={<AuthLayout />}>
<Route
path="login"
element={<Login />}
loader={redirectIfUser}
/>
<Route path="logout" />
</Route>
</Route>
)
);
// Or use plain objects
createBrowserRouter([
{
path: "/",
element: <Root />,
children: [
{
path: "contact",
element: <Contact />,
},
{
path: "dashboard",
element: <Dashboard />,
loader: ({ request }) =>
fetch("/api/dashboard.json", {
signal: request.signal,
}),
},
{
element: <AuthLayout />,
children: [
{
path: "login",
element: <Login />,
loader: redirectIfUser,
},
{
path: "logout",
action: logoutUser,
},
],
},
],
},
]);
Data Loading
loader는 컴포넌트가 렌더링 되기 전 호출되어서, useLoaderData() 함수를 통해 컴포넌트에서 데이터를 받을 수 있습니다.
<Route
path="/"
loader={async ({ request }) => {
// loaders can be async functions
const res = await fetch("/api/user.json", {
signal: request.signal,
});
const user = await res.json();
return user;
}}
element={<Root />}
>
<Route
path=":teamId"
// loaders understand Fetch Responses and will automatically
// unwrap the res.json(), so you can simply return a fetch
loader={({ params }) => {
return fetch(`/api/teams/${params.teamId}`);
}}
element={<Team />}
>
<Route
path=":gameId"
loader={({ params }) => {
// of course you can use any data store
return fakeSdk.getTeam(params.gameId);
}}
element={<Game />}
/>
</Route>
</Route>
function Root() {
const user = useLoaderData();
// data from <Route path="/">
}
function Team() {
const team = useLoaderData();
// data from <Route path=":teamId">
}
function Game() {
const game = useLoaderData();
// data from <Route path=":gameId">
}
Redirect
데이터가 로드하거나 변경하는 동안 사용자를 다른 경로로 Redirect 할 수 있습니다.
<Route
path="dashboard"
loader={async () => {
const user = await fake.getUser();
if (!user) {
// if you know you can't render the route, you can
// throw a redirect to stop executing code here,
// sending the user to a new route
throw redirect("/login");
}
// otherwise continue
const stats = await fake.getDashboardStats();
return { user, stats };
}}
/>
Data Mutation
React Router는 client side routing을 통해 Form 양식을 지원합니다. action과 결합하여, Form data submit이 가능합니다.
<Form action="/project/new">
<label>
Project title
<br />
<input type="text" name="title" />
</label>
<label>
Target Finish Date
<br />
<input type="date" name="due" />
</label>
</Form>
//////////////////////////////////////////////
<Route
path="project/new"
action={async ({ request }) => {
const formData = await request.formData();
const newProject = await createProject({
title: formData.get("title"),
due: formData.get("due"),
});
return redirect(`/projects/${newProject.id}`);
}}
/>
Optimistic UI
useFetcher를 사용하여, Optimistic UI 작업을 수행할 수 있습니다.
function LikeButton({ tweet }) {
const fetcher = useFetcher();
// if there is `formData` then it is posting to the action
const liked = fetcher.formData
? // check the formData to be optimistic
fetcher.formData.get("liked") === "yes"
: // if its not posting to the action, use the record's value
tweet.liked;
return (
<fetcher.Form method="post" action="toggle-liked">
<button
type="submit"
name="liked"
value={liked ? "yes" : "no"}
/>
</fetcher.Form>
);
}
Error Handling
라우팅 중 오류가 발생하면 경로를 렌더링하는 대신 errorElement와 같은 에러페이지를 만들어 주어서, 예외 처리를 할 수 있습니다.
<Route
path="/"
loader={() => {
something.that.throws.an.error();
}}
// this will not be rendered
element={<HappyPath />}
// but this will instead
errorElement={<ErrorBoundary />}
/>
//
출처
Feature Overview v6.8.1
Feature Overview Client Side Routing React Router enables "client side routing". In traditional websites, the browser requests a document from a web server, downloads and evaluates CSS and JavaScript assets, and renders the HTML sent from the server. When
reactrouter.com
'Javascript > React' 카테고리의 다른 글
[React] Props Drilling의 문제점과 해결방법 (0) | 2023.04.13 |
---|---|
[React] useRef Hook의 이해와 활용 (0) | 2023.03.30 |
[React] React 렌더링 최적화와 Hooks (0) | 2023.03.11 |
[React] React(Typescript)에 ESLint/Prettier/Airbnb Rule 적용하기 + Husky (2) | 2023.03.02 |
[React] ESLint와 Prettier, Git Hook을 이용한 협업 (0) | 2023.02.27 |