위의 리믹스 공식 사이트를 둘러봅시다! 세상에.. 공식 사이트가 이렇게 힙할 수 있나요...? 첫인상부터 매우 강렬하게 다가왔습니다.
이번 프로젝트에 Remix를 도입해봐야겠다고 다짐하게 된 이유는 바로
Nested Routes
때문입니다.
특히 어드민 뷰를 구현할 때, 라우트 뎁스는 깊이 이동하면서도 뷰는 동일한 구조를 갖고 있는 경우가 많습니다.
이러한 경우, 동일한 부분을 컴포넌트로 분리하여 각 페이지마다 호출을 해주어야 하는 번거로움이 있었습니다. 범용성이 좋게 구현된 컴포넌트가 아닌, 단순히 분리시키기 위한 목적의 뷰를 이렇게 컴포넌트로 분리해내는 게 과연 맞는가? 하는 의문도 매번 들었습니다.
리믹스에서는 이 번거로움을 "nested routes"로 해결하고자 합니다.
상위 라우트 페이지에서 공통된 뷰를 작성하고, 바뀌어야 하는 부분은 <Outlet />으로 넣어주면, 하위 라우트 페이지에서 작성된 뷰가 Outlet에 들어가게 됩니다.
코드로 한 번 살펴보도록 하죠!
우선은 위와 같은 구조로 라우터를 생성해보도록 하겠습니다.
/, /blog, /blog/{id} 의 세 라우터가 만들어지겠죠?
// routes/index.tsx
export default () => {
return (
<div>
index.tsx
</div>
);
};
// routes/blog.tsx
export default () => {
return (
<div>
<div>blog</div>
<Outlet />
</div>
);
};
// routes/blog/index.tsx
import { Comp } from "~/components/comp";
export default () => {
return (
<div>
blog/index.tsx <Comp />
</div>
);
};
// routes/blog/$id.tsx
export default () => {
const { id } = useParams();
return <div>blog/$id.tsx {id}</div>;
};
// components/Comp.tsx
export const Comp = () => {
return (
<div>
comp
</div>
);
};
해당 코드에 대응되는 페이지들을 한 번 보도록 하죠.
루트에서는 index.tsx 에 정의된 UI가 뜨는 것을 볼 수 있습니다.
/blog에서는 blog.tsx 와 blog/index.tsx 에 해당하는 코드 (+ import 한 컴포넌트 내부 코드)가 뜨는 것을 볼 수 있고,
/blog/{id}에서는 blog.tsx 와 blog/$id.tsx 에 해당하는 코드가 뜨는 것을 볼 수 있습니다.
blog.tsx의 코드는 blog 라우트 하위에 해당하는 뷰에서는 nest 되어 모두 뜨게 되는 것이고,
라우트에 따라서 <Outlet />에 들어가는 코드가 달라지게 되는 것입니다.
${}로 정의된 파일은 다이나믹 라우팅에 해당하는 페이지로, useParams 훅을 통해 파라미터를 가져올 수 있습니다.
간편한 Server side 데이터 로딩
도 매우 편리한 기능이었습니다.
리믹스에서 제공하는 LoaderFunction을 사용하면 쉽게 서버사이드에서 데이터를 로딩할 수 있습니다.
그리고 해당 데이터는 해당 라우트 및 해당 라우트에서 사용되는 컴포넌트 내에서 useLoaderData 훅을 사용하여 패칭 할 수 있습니다.
해당 로직을 추가하여 코드를 새로 작성해보도록 하죠.
// routes/index.tsx
import { LoaderFunction, useLoaderData } from "remix";
export const loader: LoaderFunction = () => {
return { data: "index loader" };
};
export default () => {
const data = useLoaderData();
return (
<div>
index.tsx <div>data:{data.data}</div>
</div>
);
};
// routes/blog.tsx
import { LoaderFunction, Outlet, useLoaderData, useParams } from "remix";
export const loader: LoaderFunction = () => {
return { data: "blog loader" };
};
export default () => {
const data = useLoaderData();
return (
<div>
<div>blog</div>
<div>data:{data.data}</div>
<Outlet />
</div>
);
};
// routes/blog/index.tsx
import { LoaderFunction, useLoaderData, useParams } from "remix";
import { Comp } from "~/components/comp";
export const loader: LoaderFunction = () => {
return { data: "blog/index loader" };
};
export default () => {
const data = useLoaderData();
return (
<div>
blog/index.tsx <div>data: {data}</div> <Comp />
</div>
);
};
// routes/blog/$id.tsx
import { useParams } from "remix";
export default () => {
const { id } = useParams();
return <div>blog/$id.tsx {id}</div>;
};
// components/Comp.tsx
import { useLoaderData } from "remix";
export const Comp = () => {
const data = useLoaderData();
return (
<div>
comp <div>data from router: {data.data}</div>
</div>
);
};
해당 코드에 대응되는 페이지들을 한 번 보도록 하죠.
루트에서는 index.tsx 에 정의된 loader에서 리턴하는 데이터를 훅으로 가져오는 것을 볼 수 있습니다.
/blog에서는 blog.tsx 와 blog/index.tsx 에 정의된 loader에서 리턴하는 데이터를 훅으로 가져오는 것을 볼 수 있고,
Comp 컴포넌트의 경우 라우터가 아니기 때문에 loader를 정의할 수는 없지만, 훅을 통해 데이터를 가져오는 것을 것을 볼 수 있습니다.
/blog/{id} 에서는 마찬가지로 blog.tsx 에 정의된 loader에서 리턴하는 데이터를 훅으로 가져오는 것을 볼 수 있습니다.
LoaderFunction 내부에서 ajax 호출을 하고, 해당 데이터를 컴포넌트에서 사용한다면 훨씬 더 좋은 사용자 경험을 제공할 수 있겠죠 :)
아직은 버그도 많고 탈도 많은 프레임워크지만, 강력한 기능들을 많이 제공하기에 기대가 됩니다 :)
(실제로 현재 리믹스로 프로젝트를 진행하고 있는데, 아직까지 큰 문제는 딱히 없습니다.)
새로운 프레임워크로 사이드를 진행해보고 싶다? 디자인 시안을 봤는데 nest 되는 뷰가 많다? 하시면 리믹스를 써보는 것도 좋을 것 같습니다!