TanStack Router File-Based Router Mask 完全指南

概述

TanStack Router 的 Router Mask 是一个强大但相对隐蔽的功能,它允许开发者在保持 URL 语义化的同时,实现复杂的路由交互模式。本文将深入探讨这一功能的实现原理、使用方法和最佳实践。

什么是 Router Mask?

Router Mask(路由掩码)是 TanStack Router 提供的一种特殊路由机制,它允许你:

  • 显示不同的 URL:用户在地址栏看到的 URL 与实际渲染的组件路由不同
  • 实现模态路由:点击链接时显示模态框,但 URL 变为可分享的详情页地址
  • 保持状态一致性:在模态和完整页面之间无缝切换
  • SEO 友好:每个内容都有独立的 URL,便于分享和索引

核心概念

graph TD A["用户点击链接"] --> B["Router Mask 激活"] B --> C["URL 显示: /templates/123"] B --> D["实际渲染: /templates/{$id}_modal"] C --> E["用户可分享 URL"] D --> F["模态框组件渲染"] F --> G["检测 maskedLocation"] G --> H["显示模态样式"] style A fill:#4f46e5,stroke:#312e81,stroke-width:2px,color:#fff style B fill:#059669,stroke:#047857,stroke-width:2px,color:#fff style C fill:#dc2626,stroke:#991b1b,stroke-width:2px,color:#fff style D fill:#7c3aed,stroke:#5b21b6,stroke-width:2px,color:#fff style E fill:#ea580c,stroke:#c2410c,stroke-width:2px,color:#fff style F fill:#0891b2,stroke:#0e7490,stroke-width:2px,color:#fff style G fill:#65a30d,stroke:#4d7c0f,stroke-width:2px,color:#fff style H fill:#be185d,stroke:#9d174d,stroke-width:2px,color:#fff

文件结构设计

在 file-based 路由系统中,Router Mask 需要特定的文件结构:

bash 复制代码
src/routes/
├── templates.tsx                    # 列表页面(父路由)
├── templates.{$id}_modal.tsx        # 模态路由(实际渲染)
└── templates_.$id.tsx               # 详情页面(URL 显示)

路由映射关系

文件名 路由路径 用途
templates.tsx /templates 模板列表页,包含 <Outlet />
templates.{$id}_modal.tsx /templates/{$id}_modal 模态组件,实际渲染的路由
templates_.$id.tsx /templates_/$id 详情页,URL 中显示的路径

🔍 官方文件命名约定详解 0

这是 TanStack Router file-based 路由系统的核心设计。让我们通过生成的 routeTree.gen.ts 来理解:

dart 复制代码
// 生成的路由树代码片段
const TemplatesIdRoute = TemplatesIdRouteImport.update({
  id: '/templates_/$id',        // 文件 ID(保持下划线)
  path: '/templates/$id',       // 实际 URL 路径(转换为斜杠)
  getParentRoute: () => rootRouteImport,
} as any)

const TemplatesChar123idChar125_modalRoute = TemplatesChar123idChar125_modalRouteImport.update({
  id: '/templates/{$id}_modal', // 文件 ID(保持花括号和下划线)
  path: '/{$id}_modal',         // 相对路径
  getParentRoute: () => TemplatesRoute, // 父路由是 templates
} as any)

官方命名规则详解

  1. 点号 . 分隔符 0

    • blog.post.tsx/blog/post
    • 表示嵌套路由关系,post 是 blog 的子路由
  2. $ 标识符 0

    • posts.$postId.tsx/posts/$postId
    • 带有 $ 标识符的路由段会被参数化,从 URL 路径名中提取值作为路由参数
  3. _ 前缀 0

    • _app.tsx → 无路径布局路由
    • 被视为无路径布局路由,在匹配其子路由与 URL 路径名时不会被使用
  4. _ 后缀 0

    • blog_.tsx/blog
    • 将该路由排除在任何父路由的嵌套之外
  5. 花括号 {} 特殊参数

    • {$id} 用于特殊路由处理,常用于 Router Mask 功能
    • 与普通 $id 动态参数的区别在于处理方式

为什么不能使用 templates.$id.modal.tsx? 0

ruby 复制代码
// ❌ 如果使用 templates.$id.modal.tsx
// TanStack Router 会将其解释为:
// - templates (父路由)
//   - $id (子路由,动态参数)
//     - modal (孙子路由)
// 这会创建三层嵌套:/templates/$id/modal

// ✅ 使用 templates.{$id}_modal.tsx
// TanStack Router 解释为:
// - templates (父路由)
//   - {$id}_modal (子路由,特殊参数处理)
// 这创建正确的结构:/templates/{$id}_modal

// 📝 官方规则说明:
// - $ 标识符:创建参数化路由段,从 URL 路径名提取值作为路由参数
// - {} 包裹:用于特殊参数处理,常用于 Router Mask 等高级功能
// - . 分隔符:表示嵌套路由关系

路由层级对比

graph TD subgraph "正确的文件结构" A1["templates.tsx
/templates"] --> B1["templates.{$id}_modal.tsx
/templates/{$id}_modal"] A1 --> C1["templates_.$id.tsx
/templates/$id"] end subgraph "错误的文件结构" A2["templates.tsx
/templates"] --> B2["templates.$id.tsx
/templates/$id"] B2 --> C2["templates.$id.modal.tsx
/templates/$id/modal"] end style A1 fill:#059669,stroke:#047857,stroke-width:2px,color:#fff style B1 fill:#7c3aed,stroke:#5b21b6,stroke-width:2px,color:#fff style C1 fill:#dc2626,stroke:#991b1b,stroke-width:2px,color:#fff style A2 fill:#6b7280,stroke:#374151,stroke-width:2px,color:#fff style B2 fill:#6b7280,stroke:#374151,stroke-width:2px,color:#fff style C2 fill:#ef4444,stroke:#dc2626,stroke-width:2px,color:#fff

实现原理

ini 复制代码
// templates.tsx
import { Route as TemplateModalRoute } from './templates.{$id}_modal';

<Link
  key={template.id}
  to={TemplateModalRoute.to}  // 实际导航到模态路由
  mask={{
    to: '/templates/$id',     // URL 中显示的路径
    params: { id: template.id.toString() },
  }}
  params={{ id: template.id.toString() }}
  className="group"
>
  {/* 模板卡片内容 */}
</Link>

2. 模态组件的状态检测

ini 复制代码
// templates.{$id}_modal.tsx
const TemplateModalComponent = () => {
  const { id } = useParams({ from: '/templates/{$id}_modal' });
  const router = useRouter();
  const navigate = useNavigate();

  // 关键:检测是否来自掩码路由
  const isMasked = router.state.location.maskedLocation !== undefined;
  
  const handleCloseModal = () => {
    navigate({ to: '/templates' });
  };

  useEffect(() => {
    if (isMasked) {
      // 模态模式:阻止背景滚动
      document.body.style.overflow = 'hidden';
      
      return () => {
        document.body.style.overflow = 'unset';
      };
    }
  }, [isMasked]);

  return <TemplateContent templateId={id} isMasked={isMasked} />;
};

3. 共享组件的条件渲染

ini 复制代码
// components/TemplateContent.tsx
interface TemplateContentProps {
  templateId: string;
  isMasked?: boolean;
}

const TemplateContent = ({ templateId, isMasked = false }: TemplateContentProps) => {
  const navigate = useNavigate();
  
  const handleDialogClose = () => {
    navigate({ to: '/templates' });
  };

  const content = (
    <div className="max-w-6xl mx-auto">
      {/* 面包屑导航 - 只在完整页面显示 */}
      {!isMasked && (
        <nav className="flex items-center space-x-2 text-sm text-gray-500 mb-6">
          {/* 面包屑内容 */}
        </nav>
      )}
      
      {/* 主要内容 */}
      <div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
        {/* 内容区域 */}
      </div>
    </div>
  );

  // 根据是否为模态模式选择渲染方式
  if (isMasked) {
    return (
      <Dialog open={true} onOpenChange={handleDialogClose}>
        <DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
          <DialogTitle className="sr-only">模板详情</DialogTitle>
          {content}
        </DialogContent>
      </Dialog>
    );
  }

  return (
    <div className="container mx-auto px-4 py-8">
      {content}
    </div>
  );
};

工作流程详解

rust 复制代码
sequenceDiagram
    participant U as 用户
    participant L as Link组件
    participant R as Router
    participant M as Modal组件
    participant C as Content组件
    
    U->>L: 点击模板卡片
    L->>R: 导航到 /templates/{id}_modal
    R->>R: 设置 maskedLocation = /templates/123
    R->>M: 渲染模态组件
    M->>M: 检测 isMasked = true
    M->>C: 传递 isMasked=true
    C->>C: 渲染模态样式
    C->>U: 显示模态框
    
    Note over R: URL 显示: /templates/123
    Note over M: 实际组件: TemplateModalComponent
    
    U->>C: 点击关闭按钮
    C->>R: navigate({ to: '/templates' })
    R->>R: 清除 maskedLocation
    R->>U: 返回列表页

关键技术点

1. maskedLocation 检测

ini 复制代码
const isMasked = router.state.location.maskedLocation !== undefined;

这是判断当前路由是否为掩码模式的关键代码。当 maskedLocation 存在时,说明当前是通过 mask 导航过来的。

2. 路由引用的正确方式

dart 复制代码
// ❌ 错误:硬编码路径
to="/templates/{$id}_modal"

// ✅ 正确:引用路由对象
import { Route as TemplateModalRoute } from './templates.{$id}_modal';
to={TemplateModalRoute.to}

3. 参数传递的双重配置

css 复制代码
<Link
  to={TemplateModalRoute.to}
  mask={{
    to: '/templates/$id',
    params: { id: template.id.toString() }, // mask 的参数
  }}
  params={{ id: template.id.toString() }}   // 实际路由的参数
>

最佳实践

1. 文件命名约定

  • 模态路由 :使用 {$param}_modal.tsx 格式
  • 详情页 :使用 _.$param.tsx 格式
  • 列表页 :使用简单的名称,如 templates.tsx

2. 组件复用策略

javascript 复制代码
// 创建共享的内容组件
const SharedContent = ({ data, isMasked }) => {
  // 共同的业务逻辑和 UI
};

// 模态组件
const ModalComponent = () => {
  const isMasked = router.state.location.maskedLocation !== undefined;
  return <SharedContent data={data} isMasked={isMasked} />;
};

// 详情页组件
const DetailComponent = () => {
  return <SharedContent data={data} isMasked={false} />;
};

3. 状态管理

javascript 复制代码
// 使用 useEffect 管理模态状态
useEffect(() => {
  if (isMasked) {
    // 模态打开时的副作用
    document.body.style.overflow = 'hidden';
    document.addEventListener('keydown', handleKeyDown);
    
    return () => {
      // 清理副作用
      document.body.style.overflow = 'unset';
      document.removeEventListener('keydown', handleKeyDown);
    };
  }
}, [isMasked]);

4. 键盘交互

ini 复制代码
const handleKeyDown = (e: KeyboardEvent) => {
  if (e.key === 'Escape' && isMasked) {
    navigate({ to: '/templates' });
  }
};

与传统方案对比

特性 Router Mask 传统模态 查询参数模态
URL 语义化 ⚠️
可分享性
SEO 友好 ⚠️
实现复杂度 ⚠️
状态管理 ⚠️ ⚠️
浏览器历史

官方文件命名约定完整指南 0

无路径布局路由详解

无路径路由(Pathless Routes)是 TanStack Router 的一个强大功能,它允许你创建逻辑或组件包裹器,而不影响 URL 路径。

使用 _ 前缀创建无路径路由

bash 复制代码
# 文件结构
_app.tsx                  # 无路径布局路由
_app.dashboard.tsx        # /dashboard
_app.settings.tsx         # /settings
_app.profile.tsx          # /profile

组件输出结构

文件名 路由路径 组件输出
_app.tsx 无路径 <Root><App>
_app.dashboard.tsx /dashboard <Root><App><Dashboard>
_app.settings.tsx /settings <Root><App><Settings>

实际应用示例

javascript 复制代码
// _app.tsx - 无路径布局组件
import { createFileRoute, Outlet } from '@tanstack/react-router';

const AppLayout = () => {
  return (
    <div className="min-h-screen bg-gray-50">
      {/* 全局导航栏 */}
      <nav className="bg-white shadow-sm border-b">
        <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
          {/* 导航内容 */}
        </div>
      </nav>
      
      {/* 主要内容区域 */}
      <main className="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
        <Outlet /> {/* 子路由内容 */}
      </main>
      
      {/* 全局页脚 */}
      <footer className="bg-white border-t">
        {/* 页脚内容 */}
      </footer>
    </div>
  );
};

export const Route = createFileRoute('/_app')({
  component: AppLayout,
});

路由组 (Route Groups) 0

路由组使用 (folder) 命名模式,允许你组织文件而不影响 URL 结构。

路由组示例

bash 复制代码
# 文件结构
(dashboard)/
  analytics.tsx           # /analytics
  reports.tsx            # /reports
  settings.tsx           # /settings

(auth)/
  login.tsx              # /login
  register.tsx           # /register
  forgot-password.tsx    # /forgot-password

(admin)/
  users.tsx              # /users
  roles.tsx              # /roles

路由组的优势

  1. 文件组织:按功能模块组织文件,提高可维护性
  2. URL 简洁:文件夹名不会出现在 URL 中
  3. 代码分离:不同模块的路由文件分离,避免单个目录过于庞大

目录路由文件 (.route.tsx) 0

当使用目录组织路由时,可以使用 .route.tsx 后缀创建路由文件。

目录结构示例

bash 复制代码
# 方式一:扁平文件结构
blog.post.tsx             # /blog/post

# 方式二:目录结构 + .route.tsx
blog/
  post/
    route.tsx            # /blog/post 的路由文件
    components/
      PostHeader.tsx
      PostContent.tsx
      PostComments.tsx
    hooks/
      usePost.ts
    types/
      post.types.ts

目录结构的优势

  1. 相关文件集中:路由相关的组件、hooks、类型定义都在同一目录
  2. 更好的组织:大型项目中避免根目录文件过多
  3. 团队协作:不同团队成员可以专注于不同的路由目录

索引路由 (Index Routes) 0

索引路由使用 index 标识符,在 URL 路径名与父路由完全匹配时匹配父路由。

索引路由示例

bash 复制代码
# 文件结构
blog.tsx                  # /blog (父路由)
blog.index.tsx           # /blog (索引路由,默认内容)
blog.post.tsx            # /blog/post
blog.$slug.tsx           # /blog/$slug

索引路由的作用

javascript 复制代码
// blog.tsx - 父路由
import { createFileRoute, Outlet } from '@tanstack/react-router';

const BlogLayout = () => {
  return (
    <div className="blog-layout">
      <header className="blog-header">
        <h1>我的博客</h1>
        <nav>{/* 博客导航 */}</nav>
      </header>
      <main>
        <Outlet /> {/* 子路由或索引路由内容 */}
      </main>
    </div>
  );
};

export const Route = createFileRoute('/blog')({
  component: BlogLayout,
});

// blog.index.tsx - 索引路由
import { createFileRoute } from '@tanstack/react-router';

const BlogIndex = () => {
  return (
    <div>
      <h2>欢迎来到我的博客</h2>
      <p>这里是博客首页内容...</p>
      {/* 最新文章列表 */}
    </div>
  );
};

export const Route = createFileRoute('/blog/')({
  component: BlogIndex,
});

适用场景

✅ 适合使用 Router Mask

  1. 内容详情模态:如商品详情、文章预览、用户资料等
  2. 媒体查看器:图片画廊、视频播放器等
  3. 表单编辑:需要独立 URL 的编辑表单
  4. 多步骤流程:向导式的用户流程

❌ 不适合使用 Router Mask

  1. 简单确认对话框:如删除确认、提示信息等
  2. 纯 UI 交互:如下拉菜单、工具提示等
  3. 临时状态显示:如加载状态、错误提示等
  4. 频繁切换的内容:如标签页内容切换

常见问题与解决方案

1. 文件命名错误

问题:路由无法正确生成或 mask 功能失效

常见错误命名

bash 复制代码
# ❌ 错误的命名方式
templates.$id.modal.tsx     # 会创建 /templates/$id/modal
templates-$id-modal.tsx     # 无法识别为动态路由
templates/$id_modal.tsx     # 文件系统不支持斜杠
templates.modal.$id.tsx     # 错误的嵌套顺序

正确的命名

bash 复制代码
# ✅ 正确的命名方式
templates.{$id}_modal.tsx   # 子路由,特殊参数
templates_.$id.tsx          # 平级路由,普通参数
templates.tsx               # 父路由

解决方案

typescript 复制代码
// 检查生成的 routeTree.gen.ts 文件
// 确认路由 ID 和路径是否符合预期
export interface FileRoutesById {
  '/templates/{$id}_modal': typeof TemplatesChar123idChar125_modalRoute
  '/templates_/$id': typeof TemplatesIdRoute
}

2. 模态状态检测失败

问题isMasked 始终为 false

解决方案

javascript 复制代码
// 确保正确引用路由对象
import { Route as TemplateModalRoute } from './templates.{$id}_modal';

// 检查 Link 配置
<Link
  to={TemplateModalRoute.to}  // 必须使用路由对象
  mask={{ /* ... */ }}
/>

3. 参数传递问题

问题:模态组件无法获取正确的参数

解决方案

css 复制代码
// 确保 mask 和实际路由都配置了参数
<Link
  to={TemplateModalRoute.to}
  params={{ id: template.id.toString() }}     // 实际路由参数
  mask={{
    to: '/templates/$id',
    params: { id: template.id.toString() },   // mask 路由参数
  }}
/>

4. 路由嵌套层级错误

问题:路由层级不符合预期,导致 URL 结构错误

调试方法

arduino 复制代码
// 在组件中打印路由信息
const router = useRouter();
console.log('Current route:', router.state.location);
console.log('Masked location:', router.state.location.maskedLocation);
console.log('Route ID:', router.state.matches[0]?.routeId);

5. 样式冲突

问题:模态和详情页样式互相影响

解决方案

arduino 复制代码
// 使用条件样式
const containerClass = isMasked 
  ? "modal-container" 
  : "page-container";

// 或使用 CSS-in-JS
const styles = {
  container: {
    maxWidth: isMasked ? '800px' : '1200px',
    padding: isMasked ? '0' : '2rem',
  }
};

性能优化建议

1. 代码分割

dart 复制代码
// 使用动态导入
const TemplateContent = lazy(() => import('../components/TemplateContent'));

// 在路由配置中启用预加载
export const Route = createFileRoute('/templates/{$id}_modal')({
  component: TemplateModalComponent,
  preload: true, // 预加载组件
});

2. 数据预取

dart 复制代码
// 在父路由中预取数据
export const Route = createFileRoute('/templates')({
  component: TemplatesComponent,
  loader: async () => {
    // 预取模板列表数据
    return await fetchTemplates();
  },
});

3. 状态缓存

php 复制代码
// 使用 React Query 缓存数据
const { data: template } = useQuery({
  queryKey: ['template', templateId],
  queryFn: () => fetchTemplate(templateId),
  staleTime: 5 * 60 * 1000, // 5分钟缓存
});

文件命名规则速查表

为了帮助开发者快速掌握 TanStack Router 的文件命名规则,这里提供一个完整的速查表:

基础规则 0

符号/模式 含义 示例 生成路径 说明
__root.tsx 根路由文件 __root.tsx / 必须放在 routesDirectory 根目录
. 嵌套关系(子路由) blog.post.tsx /blog/post 表示 post 是 blog 的子路由
_ 前缀 无路径布局路由 _layout.tsx 无路径 包裹子路由但不影响 URL
_ 后缀 排除父路由嵌套 blog_.tsx /blog 排除在任何父路由嵌套之外
$ 动态路径参数 posts.$postId.tsx /posts/$postId 从 URL 提取参数值
{} 特殊参数处理 posts.{$id}_modal.tsx /posts/{$id}_modal 用于 Router Mask 等特殊功能
(folder) 路由组 (auth)/login.tsx /login 文件夹不包含在 URL 路径中
index 索引路由 blog.index.tsx /blog 匹配父路由的完全路径
.route.tsx 目录路由文件 blog/post/route.tsx /blog/post 在目录结构中创建路由文件

实际应用示例 0

基础路由结构

bash 复制代码
# 根路由
__root.tsx                 # / (根路由,必需)

# 博客系统
blog.tsx                   # /blog (博客首页)
blog.post.tsx             # /blog/post (嵌套子路由)
blog.$slug.tsx            # /blog/$slug (动态参数路由)
blog.{$slug}_edit.tsx     # /blog/{$slug}_edit (特殊参数,用于模态)

# 用户系统
users.tsx                 # /users
users.$id.tsx            # /users/$id (用户详情页)
users.{$id}_profile.tsx  # /users/{$id}_profile (资料模态)

无路径布局路由示例

bash 复制代码
# 使用 _ 前缀创建无路径布局
_app.tsx                  # 无路径,布局组件
_app.dashboard.tsx        # /dashboard (被 _app 包裹)
_app.settings.tsx         # /settings (被 _app 包裹)

# 认证布局示例
_auth.tsx                 # 无路径认证布局
_auth.login.tsx          # /login (使用认证布局)
_auth.register.tsx       # /register (使用认证布局)

路由组和目录结构

bash 复制代码
# 使用 (folder) 路由组
(dashboard)/
  analytics.tsx           # /analytics (文件夹名不在 URL 中)
  reports.tsx            # /reports

# 使用 .route.tsx 文件类型
blog/
  post/
    route.tsx            # /blog/post 的路由文件
    components/          # 相关组件

索引路由示例

bash 复制代码
blog.tsx                  # /blog (父路由)
blog.index.tsx           # /blog (索引路由,匹配父路由完全路径)
blog.post.tsx            # /blog/post (子路由)

命名决策流程图

flowchart TD A["需要创建新路由"] --> B{"是否为子路由?"} B -->|是| C{"需要特殊参数?"} B -->|否| D["使用下划线 _"] C -->|是| E["使用点号 + 花括号
parent.{$param}_name.tsx"] C -->|否| F["使用点号
parent.child.tsx"] D --> G["parent_child.tsx"] E --> H["/parent/{param}_name"] F --> I["/parent/child"] G --> J["/parent/child"] style A fill:#4f46e5,stroke:#312e81,stroke-width:2px,color:#fff style E fill:#059669,stroke:#047857,stroke-width:2px,color:#fff style F fill:#7c3aed,stroke:#5b21b6,stroke-width:2px,color:#fff style G fill:#dc2626,stroke:#991b1b,stroke-width:2px,color:#fff style H fill:#ea580c,stroke:#c2410c,stroke-width:2px,color:#fff style I fill:#0891b2,stroke:#0e7490,stroke-width:2px,color:#fff style J fill:#65a30d,stroke:#4d7c0f,stroke-width:2px,color:#fff

关键记忆点 0

  1. __root.tsx:根路由文件,必须存在且位于 routesDirectory 根目录
  2. 点号 . = 嵌套关系blog.post.tsx/blog/post(post 是 blog 的子路由)
  3. _ 前缀 = 无路径布局_app.tsx → 包裹子路由但不影响 URL
  4. _ 后缀 = 排除嵌套blog_.tsx → 排除在父路由嵌套之外
  5. $ = 动态参数posts.$id.tsx/posts/$id
  6. {} = 特殊处理:用于 Router Mask 等高级功能
  7. (folder) = 路由组:文件夹名不包含在 URL 中
  8. index = 索引路由:匹配父路由的完全路径
  9. .route.tsx = 目录路由:在目录结构中创建路由文件

实用决策树

flowchart TD A["需要创建路由"] --> B{"是否为根路由?"} B -->|是| C["使用 __root.tsx"] B -->|否| D{"需要布局但不影响 URL?"} D -->|是| E["使用 _ 前缀
_layout.tsx"] D -->|否| F{"是否为子路由?"} F -->|是| G{"需要特殊参数?"} F -->|否| H{"需要动态参数?"} G -->|是| I["使用点号 + 花括号
parent.{$param}_name.tsx"] G -->|否| J["使用点号
parent.child.tsx"] H -->|是| K["使用 $ 标识符
posts.$id.tsx"] H -->|否| L["普通路由
about.tsx"] style A fill:#4f46e5,stroke:#312e81,stroke-width:2px,color:#fff style C fill:#059669,stroke:#047857,stroke-width:2px,color:#fff style E fill:#7c3aed,stroke:#5b21b6,stroke-width:2px,color:#fff style I fill:#dc2626,stroke:#991b1b,stroke-width:2px,color:#fff style J fill:#ea580c,stroke:#c2410c,stroke-width:2px,color:#fff style K fill:#0891b2,stroke:#0e7490,stroke-width:2px,color:#fff style L fill:#65a30d,stroke:#4d7c0f,stroke-width:2px,color:#fff

总结

TanStack Router 的 Router Mask 功能虽然实现相对复杂,但它提供了一种优雅的方式来处理现代 Web 应用中常见的模态路由需求。通过合理的文件结构设计、正确的组件实现和适当的状态管理,可以创建出既用户友好又 SEO 友好的交互体验。

关键要点:

  1. 理解核心概念 :掌握 maskedLocation 的工作原理
  2. 掌握文件命名规则:理解下划线、点号、花括号的不同含义
  3. 正确的文件结构:遵循命名约定和路由映射关系
  4. 组件复用:通过共享组件减少代码重复
  5. 状态管理:正确处理模态状态和副作用
  6. 性能优化:合理使用代码分割和数据缓存

虽然这种方法的学习曲线较陡,但一旦掌握,它将为你的应用带来显著的用户体验提升。特别是文件命名规则的理解,是成功实现 Router Mask 功能的基础。

相关推荐
可子是我的小猫3 小时前
【JS】模块(二)
javascript
云枫晖3 小时前
JS核心知识-执行上下文
前端·javascript
普通码农3 小时前
解决 Element Plus 分页组件英文显示问题
前端
珍珠奶茶爱好者3 小时前
vue二次封装ant-design-vue的table,识别columns中的自定义插槽
前端·javascript·vue.js
target酱3 小时前
Docker部署全流程
前端·docker
hj5914_前端新手3 小时前
React 基础 - 状态管理
前端·react.js
秃顶老男孩.3 小时前
异步处理(前端面试)
前端·面试·职场和发展
烛阴3 小时前
【TS 设计模式完全指南】用适配器模式优雅地“兼容”一切
javascript·设计模式·typescript
三脚猫的喵3 小时前
微信小程序中实现AI对话、生成3D图像并使用xr-frame演示
前端·javascript·ai作画·微信小程序