아래 내용은 코드로 배우는 React with 스프링부트 API서버 강의와 함께 설명이 부족한 부분에 대해서 조사하여 추가한 내용입니다.
const root = createBrowserRouter([
{
path: <경로>,
element: <컴포넌트>
}
...
])
createBrowserRouter()
은 라우팅을 정의하기 위해 사용합니다. 이때, 라우터가 데이터를 보여주기 위해서는 페이지 초기화가 필요한데, 함수의 첫 번째 인자에 배열을 넘겨줌으로써 다수의 컴포넌트들이 올 수 있고, 만약 많은 수의 페이지들이 존재한다면 그 만큼 속도가 느려지게 되는데 이를 위해 Code Splitting 이라는 것을 사용합니다.
이렇게 만든 라우터는 아래처럼 <RouterProvider router={컴포넌트명} />
를 통해 등록할 수 있습니다.
function App() {
return (
<RouterProvider router={root} />
);
}
Code Splitting 은 필요할 때까지 로딩하지 않는 것을 의미하는데 이는 React.lazy 와 Suspense 를 사용하여 구현할 수 있습니다.
동적 import 를 호출하는 함수를 인자로 가져 필요할 때 컴포넌트를 렌더링 할 수 있도록 도와줍니다. lazy 컴포넌트는 Suspense 컴포넌트 하위에서 렌더링 되어야 합니다.
lazy 컴포넌트를 기다리는 동안 fallback 을 통해 로딩 중에 보여줄 UI 의 코드를 지정할 수 있습니다.
import { Suspense, lazy } from "react";
import { createBrowserRouter } from "react-router-dom";
const Loading = <div>Loading...</div>
const Main = lazy( () => import("../pages/MainPage"))
const root = createBrowserRouter([
{
path: '',
element: <Suspense fallback={Loading}><Main /></Suspense>
}
])
export default root;
function MainPage() {
return (
<BasicLayout>
<div className={'text-3xl'}>Main Page</div>
</BasicLayout>
)
}
위의 예시에서 BasicLayout 컴포넌트를 사용하면서 그 하위에 div 태그를 사용하였습니다. 이렇게 사용하면 BasicLayout 컴포넌트에서 children 을 이용하여 위의 div 태그에 해당하는 내용을 사용할 수 있게 됩니다.
function BasicLayout({ children }) {
return (
<>
<BasicMenu />
...
<main className="bg-sky-300 md:w-2/3 lg:w-3/4 px-5 py-40">
{children}
</main>
...
</>
);
}
컴포넌트는 children 속성으로 내부에 다른 컴포넌트를 적용할 수 있습니다. 위의 코드에서 전달받은 children 을 사용해서 코드를 작성하는 것을 볼 수 있습니다. 이때 공통 레이아웃에서 변경되는 부분만 children 을 사용하고, 공통적인 부분은 위의 BasicMenu 와 같이 import 해서 사용하면 됩니다.
function BasicMenu(props) {
return (
...
<li className="pr-6 text-2xl">
<Link to={'/'}>Main</Link>
</li>
<li className="pr-6 text-2xl">
<Link to={'/about'}>About</Link>
</li>
);
}
우리가 웹 브라우저에서 url 을 변경하게 되면 브라우저는 기존에 있는 데이터를 지우고 다시 데이터를 가져와 우리에게 보여주게 됩니다. 리액트는 SPA 이기 때문에 경로가 변경될 때마다 다시 실행을 하게 되면 결국 리액트를 다시 실행하는게 됩니다. 이를 피하기 위해 Link 를 사용하게 됩니다.
중첩 라우팅이란 쉽게 말해서 url 을 단계적으로 정의할 수 있는 기법이라고 생각하면 됩니다. 예를 들어,
/todo
라는 상위 url 하위에/list
를 두어/todo/list
에 해당하는 컴포넌트를 보여줄 수 있습니다.
const root = createBrowserRouter([
...
{
path: 'todo',
element: <Suspense fallback={Loading}><TodoIndex /></Suspense>,
children: [
{
path: 'list',
element: <Suspense fallback={Loading}><ToDoList /></Suspense>
}
]
}
])
위의 예시는 라우터에서 중첩 라우팅을 지정하는 방법입니다. children 의 경로는 상위에 묶이기 때문에 children 에 객체를 추가하고, path 를 입력하면 상위path/하위path
를 의미하게 됩니다.
/todo
라는 경로로 들어왔을 때는 TodoIndex 컴포넌트를 보여주고, 그 하위에서 경로가 변경되면 children 에 정의된 element 가 다시 렌더링됩니다.
/todo
하위의 경로의 페이지들이 많아 정의해야 할 라우팅 정보가 많다면 아래처럼 따로 함수를 이용해 분리할 수 있습니다. children 에는 배열이 들어가기 때문에 함수가 반환하는 것 역시 배열이 됩니다.
const root = createBrowserRouter([
{
path: 'todo',
element: <Suspense fallback={Loading}><TodoIndex /></Suspense>,
children: todoRouter()
}
...
]);
const todoRouter = () => {
return [
{
path: 'list',
element: <Suspense fallback={Loading}><ToDoList /></Suspense>
}
...
]
}
Outlet 컴포넌트는 라우팅 컴포넌트 내부에서 사용되고, 중첩 라우팅으로 인한 하위 경로가 렌더링 될 때 컴포넌트가 표시되는 위치를 지정할 수 있습니다. 예를 들어,
/todo
로 시작하는 경로에서만 사용되는 레이아웃을 화면에 표시할 때<Outlet />
컴포넌트를 사용합니다.
function ToDoIndexPage() {
return (
<BasicLayout>
<div className='w-full flex m-2 p-2'>
<div className='text-xl m-1 p-2 w-20 font-extrabold text-center underline'>
List
</div>
<div className='text-xl m-1 p-2 w-20 font-extrabold text-center underline'>
Add
</div>
</div>
<div className='flex flex-wrap w-full'>
<Outlet />
</div>
</BasicLayout>
)
}
위의 예시는 /todo
라는 url 로 들어왔을 때 표현되는 컴포넌트입니다. 그리고 이 컴포넌트가 /todo
경로로 시작하는 페이지들의 기본적인 레이아웃이 됩니다. 즉, /todo/list
나 /todo/add
에 해당하는 컴포넌트들이 바로 저 Outlet 의 위치에 렌더링되게 됩니다. 그림으로 표현하면 아래와 같습니다.
이렇게 사용했을 때의 장점은 지금 상단의 메뉴를 유지하기 위해 위의 ToDoIndexPage 처럼 <BasiLayout>
으로 감싸주고, 그 하위의 요소들이 <BasicLayout>
의 children 에서 렌더링 되도록 설정되어 있습니다. 하지만 <Outlet>
을 사용함으로써 <BasicLayout>
으로 감싸주지 않아도 상단 메뉴가 유지되며, 중첩 UI 를 렌더링 할 수 있게 됩니다.
function ListPage() {
return (
<div className='p-4 full bg-white'>
<div className='text-3xl font-extrabold'>
ToDo List Page Component
</div>
</div>
)
}
위의 예시는 /todo/list
에 해당하는 컴포넌트인데 위에서 언급한 것처럼 <BasicLayout>
으로 감싸주지 않았습니다. 하지만 상단의 메뉴는 정상적으로 출력됩니다. 이것이 가능한 이유는 ListPage 컴포넌트가 ToDoIndexPage 컴포넌트의 <Outlet>
에 들어가기 때문입니다.
Navigate 는 해당 경로로 들어왔을 때 리다이렉션이 일어나게 도와줍니다.
{
path: '',
element: <Navigate replace={true} to={'list'}></Navigate>
}
위의 코드는 /todo
로 url 이 들어왔을 때 동작하는 부분의 일부입니다. 해당 태그의 replace 는 현재 경로 대신 지정된 경로를 보여준다는 의미이고, to 는 어디로 리다이렉션을 할 지 정의하는 부분입니다. 위의 예시에서는 /todo
가 /todo/list
로 변경됩니다.
useParams 는 React-Router 를 사용할 때
path: 'read/:toDoNo'
처럼 path 에:
을 사용한 경우, URL 에 포함된 해당 값을 얻고 싶을 때 사용합니다.
const {toDoNo} = useParams(); // 구조분해할당
useParmas()
는 object 를 반환하는데 {}
를 사용해 구조분해 할당으로 object 내부에 있는 값을 꺼내서 사용할 수 있습니다.
useSearchParams 는 URL 의 쿼리 스트링(쿼리 파라미터)를 읽고, 수정하는데 사용합니다.
const [searchParams, setSearchParams] = useSearchParams();
const page = searchParams.get('page') ? parseInt(searchParams.get('page')) : 1;
useState()
처럼 변수와 setter함수 두 가지를 반환하는데 get(key)
를 통해 url 의 key 에 해당하는 value 를 가져올 수 있습니다.
useNavigate 는 페이지를 이동할 때 사용되며, 특정 이벤트가 실행됐을 때( 조건을 만족했을 때 ) 동작하도록 설정할 수 있는 인터페이스를 제공합니다.
const navigate = useNavigate();
const handleModifyClick = (no) => {
navigate({
pathname: `/todo/modify/${no}`,
search: queryString
})
}
해당 부분을 공부하면서 페이지 이동이라면 위에서 배운 방법도 있는데 왜 사용할까라는 의문이 들었는데 구글링 결과 useNavigate 는 state 를 사용해서 페이지 간에 props 를 자유롭게 전달할 수 있다는 장점이 있어 사용한다는 것을 알게 되었습니다.
위의 예시는 현재 url 이전 url 에 있는 쿼리 스트링을 유지하면서 새로운 경로로 이동하는 예시입니다. navigate({...})
의 {...}
안에 pathname 과 search 를 지정할 수 있는데 path 는 경로를 지정하고 search 는 쿼리 스트링을 지정합니다.