Remix路由基础 #
一、路由概述 #
Remix 使用基于文件系统的路由,app/routes 目录下的文件自动映射为 URL 路径。
二、路由文件命名规则 #
2.1 基本规则 #
| 文件名 | URL路径 | 说明 |
|---|---|---|
_index.tsx |
/ |
首页 |
about.tsx |
/about |
关于页面 |
contact.tsx |
/contact |
联系页面 |
blog.tsx |
/blog |
博客页面 |
2.2 目录结构 #
使用目录组织路由:
text
app/routes/
├── _index.tsx # /
├── about.tsx # /about
├── blog/
│ ├── _index.tsx # /blog
│ ├── $slug.tsx # /blog/:slug
│ └── new.tsx # /blog/new
└── admin/
├── _index.tsx # /admin
└── users.tsx # /admin/users
2.3 点号表示法 #
也可以使用点号代替目录:
text
app/routes/
├── _index.tsx # /
├── blog._index.tsx # /blog
├── blog.$slug.tsx # /blog/:slug
├── blog.new.tsx # /blog/new
└── admin.users.tsx # /admin/users
三、导航组件 #
3.1 Link组件 #
使用 Link 进行客户端导航:
tsx
import { Link } from "@remix-run/react";
export default function Navbar() {
return (
<nav>
<Link to="/">首页</Link>
<Link to="/about">关于</Link>
<Link to="/blog">博客</Link>
</nav>
);
}
3.2 NavLink组件 #
NavLink 提供激活状态:
tsx
import { NavLink } from "@remix-run/react";
export default function Navbar() {
return (
<nav>
<NavLink
to="/"
className={({ isActive, isPending }) =>
isActive ? "active" : isPending ? "pending" : ""
}
>
首页
</NavLink>
<NavLink
to="/about"
style={({ isActive }) => ({
color: isActive ? "red" : "black",
})}
>
关于
</NavLink>
</nav>
);
}
3.3 编程式导航 #
使用 useNavigate Hook:
tsx
import { useNavigate } from "@remix-run/react";
export default function LoginForm() {
const navigate = useNavigate();
const handleSubmit = async (event: React.FormEvent) => {
event.preventDefault();
const success = await login();
if (success) {
navigate("/dashboard");
}
};
return (
<form onSubmit={handleSubmit}>
<button type="submit">登录</button>
</form>
);
}
3.4 Navigate组件 #
使用 Navigate 组件进行重定向:
tsx
import { Navigate } from "@remix-run/react";
export default function OldPage() {
const shouldRedirect = true;
if (shouldRedirect) {
return <Navigate to="/new-page" replace />;
}
return <div>旧页面</div>;
}
四、路由参数 #
4.1 获取当前路径 #
使用 useLocation Hook:
tsx
import { useLocation } from "@remix-run/react";
export default function Breadcrumb() {
const location = useLocation();
return (
<div>
当前路径: {location.pathname}
搜索参数: {location.search}
哈希: {location.hash}
</div>
);
}
4.2 获取路由信息 #
使用 useMatches Hook:
tsx
import { useMatches } from "@remix-run/react";
export default function Breadcrumb() {
const matches = useMatches();
return (
<nav>
{matches.map((match) => (
<span key={match.id}>
{match.pathname} >
</span>
))}
</nav>
);
}
4.3 预加载资源 #
Link 组件支持预加载:
tsx
import { Link } from "@remix-run/react";
export default function PostList({ posts }) {
return (
<ul>
{posts.map((post) => (
<li key={post.id}>
<Link
to={`/posts/${post.id}`}
prefetch="intent"
>
{post.title}
</Link>
</li>
))}
</ul>
);
}
预加载选项:
| 选项 | 说明 |
|---|---|
none |
不预加载(默认) |
intent |
悬停或聚焦时预加载 |
render |
渲染时立即预加载 |
viewport |
进入视口时预加载 |
五、路由配置 #
5.1 路由句柄 #
使用 handle 导出添加路由元数据:
tsx
import type { RouteHandle } from "@remix-run/node";
export const handle: RouteHandle = {
breadcrumb: "文章列表",
showSidebar: true,
};
export default function Posts() {
return <div>文章列表</div>;
}
在父组件中使用:
tsx
import { useMatches } from "@remix-run/react";
export default function Breadcrumb() {
const matches = useMatches();
return (
<nav>
{matches
.filter((match) => match.handle?.breadcrumb)
.map((match) => (
<span key={match.id}>
{match.handle.breadcrumb}
</span>
))}
</nav>
);
}
5.2 路由模块导出 #
每个路由文件可以导出以下内容:
tsx
// 元数据
export const meta: MetaFunction = () => [{ title: "页面标题" }];
// 资源链接
export const links: LinksFunction = () => [];
// 数据加载
export async function loader({ params, request }: LoaderFunctionArgs) {}
// 数据变更
export async function action({ request, params }: ActionFunctionArgs) {}
// 错误边界
export function ErrorBoundary() {}
// 路由句柄
export const handle = {};
// 页面组件
export default function Page() {}
六、导航最佳实践 #
6.1 使用Link而非a标签 #
tsx
// 推荐
<Link to="/about">关于</Link>
// 不推荐(会导致整页刷新)
<a href="/about">关于</a>
6.2 合理使用预加载 #
tsx
// 高优先级页面
<Link to="/dashboard" prefetch="render">
仪表板
</Link>
// 低优先级页面
<Link to="/settings" prefetch="intent">
设置
</Link>
6.3 处理外部链接 #
tsx
function ExternalLink({ href, children }) {
return (
<a
href={href}
target="_blank"
rel="noopener noreferrer"
>
{children}
</a>
);
}
七、路由守卫 #
7.1 在loader中重定向 #
tsx
import { redirect } from "@remix-run/node";
export async function loader({ request }) {
const user = await getUser(request);
if (!user) {
const url = new URL(request.url);
return redirect(`/login?redirect=${url.pathname}`);
}
return { user };
}
7.2 使用中间件模式 #
tsx
export async function loader({ request }) {
const user = await requireUser(request);
const data = await fetchData(user.id);
return { data };
}
async function requireUser(request: Request) {
const user = await getUser(request);
if (!user) {
throw redirect("/login");
}
return user;
}
八、总结 #
本章我们学习了:
- 文件系统路由:文件名映射为URL路径
- 导航组件:Link、NavLink、useNavigate
- 路由参数:useLocation、useMatches
- 预加载:优化页面切换性能
- 路由守卫:在loader中实现权限控制
核心要点:
- 使用
Link进行客户端导航 - 合理使用预加载提升用户体验
- 在 loader 中处理权限和重定向
最后更新:2026-03-28