안녕하세요, 오늘은 프로젝트 제작을 위해 리액트 스터디를 진행했습니다. 그 중 React Router에 대해서 스터디 한 내용에 대해서 정리해보려고 합니다.
리액트 라우터 (React Router)란?
리액트 라우터는 React 애플리케이션에서 페이지 이동을 관리하는 데 사용되는 라이브러리입니다. 이전의 웹 애플리케이션에서는 페이지 이동 시마다 서버에 요청을 보내고 새로운 페이지를 로드하지만, 리액트 라우터를 사용하면 SPA(Single Page Application) 형식으로 페이지를 이동할 수 있게 됩니다.
리액트 라우터 설치하기
리액트 라우터 공식 홈페이지를 참고하였습니다.
https://reactrouter.com/en/main
리액트 라우터를 사용하기 위해서는 react-router-dom
패키지를 설치해야 합니다.
npm install react-router-dom
설치 후, 애플리케이션의 엔트리 파일 (보통 index.js
또는 App.js
)에서 RouterProvider
를 사용하여 라우터를 설정합니다.
그럼 본격적으로 리액트 라우터를 활용하는 방법들에 대해서 공부한 내용을 바탕으로 정리해 보도록 하겠습니다.
리액트 라우터 활용 방법
Link 컴포넌트
우선 리액트 라우터에서 제공하는 링크 컴포넌트에 대해서 소개하고, 활용하는 방법에 대해서 알아보겠습니다. 링크 컴포넌트는 페이지 간의 내부 링크를 생성하며, 브라우저의 기본 동작을 방지하여 페이지 전환 시 전체 페이지를 다시 로드하지 않습니다. <a>
태그를 대체하여 클릭 시 페이지 전환을 처리하는 방식입니다.링크 컴포넌트는 to
속성을 사용하여 링크할 경로를 지정합니다. to
속성에는 경로 문자열이나 객체를 전달할 수 있으며, 객체를 사용하면 동적 경로 생성이 가능합니다. 예를 들어, to={{ pathname: '/products', search: '?category=shoes' }}
와 같이 사용할 수 있습니다.
useParams
리액트 라우터를 통해 URL 파라미터와 쿼리 스트링을 쉽게 사용할 수 있습니다. URL 파라미터는 주로 특정 리소스를 식별할 때 사용됩니다. useParams
는 React Router에서 제공하는 훅(hook)으로, 현재 URL의 매개변수에 접근할 수 있게 해줍니다. 따라서 URL의 동적 세그먼트 값을 쉽게 추출할 수 있습니다.
// User.js
import React from 'react';
import { useParams } from 'react-router-dom';
const User = () => {
const { id } = useParams();
return (
<div>
<h2>User ID: {id}</h2>
</div>
);
};
export default User;
useSearchParams
useSearchParams를 사용한 쿼리 스트링 생성하는 과정에 대해서 정리해보려고 합니다.
useSearchParams
함수는 쿼리 스트링을 객체 형태로 생성하여 URL에 반영할 수 있도록 도와주는 함수입니다.
import React from 'react';
import { useSearchParams } from 'react-router-dom';
const SearchPage = () => {
const [searchParams, setSearchParams] = useSearchParams();
const handleSearch = (event) => {
event.preventDefault();
const formData = new FormData(event.target);
const searchQuery = formData.get('query');
// 동적으로 쿼리 스트링 설정
setSearchParams({ query: searchQuery });
};
return (
<div>
<h2>Search Page</h2>
<form onSubmit={handleSearch}>
<input type="text" name="query" placeholder="Search..." />
<button type="submit">Search</button>
</form>
{/* 결과 출력 */}
<SearchResults searchParams={searchParams} />
</div>
);
};
const SearchResults = ({ searchParams }) => {
return (
<div>
<h3>Search Results</h3>
<p>Query: {searchParams.get('query')}</p>
{/* 검색 결과 표시 */}
</div>
);
};
export default SearchPage;
우선 useSearchParams
훅을 사용하여 현재 URL의 쿼리 스트링을 가져오고 설정할 수 있습니다. 해당 예제코드에서는 폼 제출 시 호출되며, 입력된 검색어를 가져와 setSearchParams
함수를 사용하여 쿼리 스트링을 업데이트합니다. 그 다음 SearchResults 컴포넌트 에서 searchParams
를 props로 전달받아 현재 쿼리 스트링 값을 표시합니다. 주로 검색, 필터링, 페이지네이션 등의 기능을 쉽게 구현할 수 있도록 도와주는 함수입니다.
중첩 라우트
중첩 라우트(Nested Routes)는 한 라우트 내에서 다른 라우트들을 중첩하여 구성하는 기법을 말합니다.예를들어 한 페이지나 컴포넌트 안에서 하위 컴포넌트들이 서로 다른 경로에 대응되도록 할 때 사용됩니다.
우선 저의 실습 코드로 설명드리겠습니다.
const TodoList = lazy(() => import("../pages/todo/ListPage"))
const root = createBrowserRouter([
{
path: "todo",
element: <Suspense fallback={Loading}><TodoIndex/></Suspense>,
children: [
{
path: "list",
element: <Suspense fallback={Loading}> <TodoList/> </Suspense>
}
]
}
])
export default root;
다음과 같이 todo 라는 주소 안에 children 으로 list 라는 주소를 추가했습니다. 이런식으으로 children 이라는 속성내에 배열로 계속해서 todo 하위의 path 들을 추가해갈 수 있습니다. 다만 점진적으로 todo 내부 depth가 증가하게된다면, 계층 구조 파악이 어려워질 수 있습니다. 따라서 다음과 같이 함수로 분리할 수 있습니다.
별도의 스크립트로 분리하고 todoRouter 라는 함수를 생성합니다. 여기서 todo 내부 depth 에 따른 path 들을 정의해줍니다. 이후 본래의 부모 라우터의 children 속성에 다음과같이 생성한 함수를 호출하여 관리할 수 있습니다.
useNavigates
다음으로는 useNavigate
를 활용한 방식에 대해서 소개하려고 합니다.
useNavigate
함수 내에서 라우터를 조작할 수 있습니다. 예를 들어서 사용자의 상호작용이나 비동기 작업 완료 후에 페이지를 변경하는 등의 작업을 수행할 수 있습니다. 실습한 코드와 함께 설명하도록 하겠습니다.
import { useCallback } from "react";
import { useNavigate, useParams } from "react-router-dom";
const ReadPage = () => {
const {tno} = useParams()
const navigate = useNavigate()
const moveToModify = useCallback((tno) => {
navigate({pathname:`/todo/modify/${tno}`})
},[tno])
return (
<div className="text-3xl font-extrabold"> Todo Read Page Component {tno}
<div> <button onClick={() => moveToModify(33)}>Test Modify</button> </div>
</div>
);
}
export default ReadPage;
여기서 저는 useParams
후크도 함께 사용하여 URL 파라미터(tno
)를 가져오고, 이를 기반으로 다른 경로로 이동하도록 하였습니다.
navigate
함수는 객체를 인자로 받습니다. 여기서 pathname
속성을 사용하여 이동할 경로를 지정했습니다. ${tno}
변수를 이용하여 동적으로 경로를 생성했습니다. 그 다음 moveToModify
함수를 선언했습니다. 버튼 클릭 시 moveToModify
함수가 호출되도록 이벤트를 등록해주었습니다. 이로 인해 moveToModify
함수는 navigate({ pathname:
/todo/modify/${tno} });
코드를 실행하여 /todo/modify/33
경로로 이동할 수 있게 하였습니다.
Outlet
다음은 Outlet에 대해서 공부한 내용 입니다. Outlet
은 라우터 구조를 위한 컴포넌트입니다. 일반적으로 라우터 계층 구조 내에서 사용되며, 특히 중첩된 라우트 구조에서 유용하게 활용됩니다.
Outlet 의 역할
outlet의 주요 역할에 대해서 알아보겠습니다.
라우트 중첩 처리: Outlet
은 중첩된 라우트에서 부모 라우터가 자식 라우터의 컴포넌트들을 렌더링할 위치를 정의합니다. 즉, 부모 라우터가 Outlet
을 통해 자식 라우터의 컴포넌트들을 표시할 수 있습니다.
다중 렌더링 처리: 한 라우트 컴포넌트 안에 여러 개의 하위 라우트가 있을 때, Outlet
은 이들을 순서대로 렌더링합니다. 예를 들어, /dashboard
라우트에 /dashboard/profile
, /dashboard/settings
등이 중첩되어 있다면, /dashboard
컴포넌트에는 Outlet
을 사용하여 중첩된 하위 라우트들을 표시할 수 있습니다. 실습 코드와 함께 살펴보겠습니다.
Outlet 예제코드
다음과 같이 IndexPage 에 Outlet 컴포넌트를 사용했습니다. 이로 인해 사용자가 /list
또는 /add
경로로 이동하면, IndexPage
컴포넌트가 렌더링되고, ListPage
또는 AddPage
컴포넌트는 IndexPage
내의 <Outlet />
위치에 렌더링됩니다. 이 실습코드에서 <Outlet />
은 ListPage
또는 AddPage
와 같은 하위 라우트를 표현하는 위치가 됩니다.
따라서 다음과 같이 IndexPage 에서 List 버튼 클릭 시
Outlet 컴포넌트로 인해 해당 위치에 ListPage 가 렌더링이 되고, Add 클릭 시
Outlet 컴포넌트 위치에 AddPage가 렌더링 됩니다.