路由概念

TanStack Router 支持许多强大的路由概念,使您可以轻松构建复杂和动态的路由系统。

这些概念都非常有用且功能强大,我们将在以下章节中深入探讨它们。

路由的结构

除了根路由之外的所有其他路由,都使用createFileRoute函数进行配置,该函数在使用基于文件的路由时提供类型安全

tsx
import { createFileRoute } from '@tanstack/solid-router'

export const Route = createFileRoute('/posts')({
  component: PostsComponent,
})
import { createFileRoute } from '@tanstack/solid-router'

export const Route = createFileRoute('/posts')({
  component: PostsComponent,
})

createFileRoute函数接受一个参数,即文件路由的路径字符串。

❓❓❓ “等等,您要我将路由文件的路径传递给createFileRoute?”

是的!但是不用担心,此路径由 TanStack Router Bundler 插件或 Router CLI 为您自动编写和管理。 因此,当您创建新路由、移动路由或重命名路由时,路径将自动为您更新。

此路径名的原因与 TanStack Router 的神奇类型安全有关。如果没有此路径名,TypeScript 将不知道我们在哪个文件中!(我们希望 TypeScript 有一个内置功能来实现这一点,但他们还没有 🤷‍♂️)

根路由

根路由是整个树中最顶层的路由,并将所有其他路由作为子路由封装。

  • 它没有路径
  • 始终匹配
  • 它的component始终渲染

即使它没有路径,根路由也可以访问与其他路由相同的所有功能,包括

  • 组件
  • 加载器
  • 搜索参数验证
  • 等等。

要创建根路由,请调用createRootRoute()函数,并将其作为路由文件中的Route变量导出

tsx
// Standard root route
import { createRootRoute } from '@tanstack/solid-router'

export const Route = createRootRoute()

// Root route with Context
import { createRootRouteWithContext } from '@tanstack/solid-router'
import type { QueryClient } from '@tanstack/react-query'

export interface MyRouterContext {
  queryClient: QueryClient
}
export const Route = createRootRouteWithContext<MyRouterContext>()
// Standard root route
import { createRootRoute } from '@tanstack/solid-router'

export const Route = createRootRoute()

// Root route with Context
import { createRootRouteWithContext } from '@tanstack/solid-router'
import type { QueryClient } from '@tanstack/react-query'

export interface MyRouterContext {
  queryClient: QueryClient
}
export const Route = createRootRouteWithContext<MyRouterContext>()

要了解有关 TanStack Router 中 Context 的更多信息,请参阅Router 上下文指南。

基本路由

基本路由匹配特定路径,例如/about/settings/settings/notifications都是基本路由,因为它们完全匹配路径。

让我们看一下/about路由

tsx
// about.tsx
import { createFileRoute } from '@tanstack/solid-router'

export const Route = createFileRoute('/about')({
  component: AboutComponent,
})

function AboutComponent() {
  return <div>About</div>
}
// about.tsx
import { createFileRoute } from '@tanstack/solid-router'

export const Route = createFileRoute('/about')({
  component: AboutComponent,
})

function AboutComponent() {
  return <div>About</div>
}

基本路由简单明了。它们完全匹配路径并渲染提供的组件。

索引路由

父路由完全匹配且没有子路由匹配时,索引路由专门针对其父路由。

让我们看一下/posts URL 的索引路由

tsx
// posts.index.tsx
import { createFileRoute } from '@tanstack/solid-router'

// Note the trailing slash, which is used to target index routes
export const Route = createFileRoute('/posts/')({
  component: PostsIndexComponent,
})

function PostsIndexComponent() {
  return <div>Please select a post!</div>
}
// posts.index.tsx
import { createFileRoute } from '@tanstack/solid-router'

// Note the trailing slash, which is used to target index routes
export const Route = createFileRoute('/posts/')({
  component: PostsIndexComponent,
})

function PostsIndexComponent() {
  return <div>Please select a post!</div>
}

当 URL 完全是/posts时,将匹配此路由。

动态路由段

$开头后跟标签的路由路径段是动态的,并将 URL 的该部分捕获到params对象中,以在您的应用程序中使用。例如,/posts/123的路径名将匹配/posts/$postId路由,并且params对象将是{ postId: '123' }

然后可以在路由的配置和组件中使用这些参数!让我们看一下posts.$postId.tsx路由

tsx
import { createFileRoute } from '@tanstack/solid-router'

export const Route = createFileRoute('/posts/$postId')({
  // In a loader
  loader: ({ params }) => fetchPost(params.postId),
  // Or in a component
  component: PostComponent,
})

function PostComponent() {
  // In a component!
  const { postId } = Route.useParams()
  return <div>Post ID: {postId}</div>
}
import { createFileRoute } from '@tanstack/solid-router'

export const Route = createFileRoute('/posts/$postId')({
  // In a loader
  loader: ({ params }) => fetchPost(params.postId),
  // Or in a component
  component: PostComponent,
})

function PostComponent() {
  // In a component!
  const { postId } = Route.useParams()
  return <div>Post ID: {postId}</div>
}

🧠 动态段在路径的每个段上工作。例如,您可以拥有一个路径为/posts/$postId/$revisionId的路由,并且每个$段都将被捕获到params对象中。

Splat / Catch-All 路由

路径仅为$的路由称为“splat”路由,因为它始终$到结尾捕获 URL 路径名的任何剩余部分。然后,捕获的路径名在params对象中的特殊_splat属性下可用。

例如,以files/$路径为目标的路由是 splat 路由。如果 URL 路径名为/files/documents/hello-world,则params对象将包含documents/hello-world在特殊的_splat属性下

js
{
  '_splat': 'documents/hello-world'
}
{
  '_splat': 'documents/hello-world'
}

⚠️ 在 router v1 中,splat 路由也用*而不是_splat键表示,以实现向后兼容性。这将在 v2 中删除。

🧠 为什么要使用$? 感谢像 Remix 这样的工具,我们知道尽管*是最常用的字符来表示通配符,但它们与文件名或 CLI 工具不兼容,因此就像它们一样,我们决定改用$

布局路由

布局路由用于使用其他组件和逻辑包装子路由。它们对于以下情况很有用

  • 使用布局组件包装子路由
  • 在显示任何子路由之前强制执行loader要求
  • 验证和向子路由提供搜索参数
  • 为子路由提供错误组件或挂起元素的回退
  • 为所有子路由提供共享上下文
  • 以及更多!

让我们看一下名为app.tsx的布局路由示例

routes/
├── app.tsx
├── app.dashboard.tsx
├── app.settings.tsx
routes/
├── app.tsx
├── app.dashboard.tsx
├── app.settings.tsx

在上面的树中,app.tsx是一个布局路由,它包装了两个子路由:app.dashboard.tsxapp.settings.tsx

此树结构用于使用布局组件包装子路由

tsx
import { Outlet, createFileRoute } from '@tanstack/solid-router'

export const Route = createFileRoute('/app')({
  component: AppLayoutComponent,
})

function AppLayoutComponent() {
  return (
    <div>
      <h1>App Layout</h1>
      <Outlet />
    </div>
  )
}
import { Outlet, createFileRoute } from '@tanstack/solid-router'

export const Route = createFileRoute('/app')({
  component: AppLayoutComponent,
})

function AppLayoutComponent() {
  return (
    <div>
      <h1>App Layout</h1>
      <Outlet />
    </div>
  )
}

下表显示了基于 URL 将渲染哪个(些)组件

URL 路径组件
/<Index>
/app/dashboard<AppLayout><Dashboard>
/app/settings<AppLayout><Settings>

由于 TanStack Router 支持混合平面和目录路由,因此您也可以在目录中使用布局路由来表达应用程序的路由

routes/
├── app/
│   ├── route.tsx
│   ├── dashboard.tsx
│   ├── settings.tsx
routes/
├── app/
│   ├── route.tsx
│   ├── dashboard.tsx
│   ├── settings.tsx

在此嵌套树中,app/route.tsx文件是布局路由的配置,该布局路由包装了两个子路由:app/dashboard.tsxapp/settings.tsx

布局路由还允许您为动态路由段强制执行组件和加载器逻辑

routes/
├── app/users/
│   ├── $userId/
|   |   ├── route.tsx
|   |   ├── index.tsx
|   |   ├── edit.tsx
routes/
├── app/users/
│   ├── $userId/
|   |   ├── route.tsx
|   |   ├── index.tsx
|   |   ├── edit.tsx

无路径布局路由

布局路由类似,无路径布局路由用于使用其他组件和逻辑包装子路由。但是,无路径布局路由不需要 URL 中有匹配的path,并且用于使用其他组件和逻辑包装子路由,而无需 URL 中有匹配的path

无路径布局路由以下划线 (_) 为前缀,表示它们是“无路径的”。

🧠 _前缀后的路径部分用作路由的 ID,并且是必需的,因为每个路由都必须是唯一可识别的,尤其是在使用 TypeScript 时,以避免类型错误并有效地完成自动完成。

让我们看一下名为_pathlessLayout.tsx的路由示例


routes/
├── _pathlessLayout.tsx
├── _pathlessLayout.a.tsx
├── _pathlessLayout.b.tsx

routes/
├── _pathlessLayout.tsx
├── _pathlessLayout.a.tsx
├── _pathlessLayout.b.tsx

在上面的树中,_pathlessLayout.tsx是一个无路径布局路由,它包装了两个子路由:_pathlessLayout.a.tsx_pathlessLayout.b.tsx

_pathlessLayout.tsx路由用于使用无路径布局组件包装子路由

tsx
import { Outlet, createFileRoute } from '@tanstack/solid-router'

export const Route = createFileRoute('/_pathlessLayout')({
  component: PathlessLayoutComponent,
})

function PathlessLayoutComponent() {
  return (
    <div>
      <h1>Pathless layout</h1>
      <Outlet />
    </div>
  )
}
import { Outlet, createFileRoute } from '@tanstack/solid-router'

export const Route = createFileRoute('/_pathlessLayout')({
  component: PathlessLayoutComponent,
})

function PathlessLayoutComponent() {
  return (
    <div>
      <h1>Pathless layout</h1>
      <Outlet />
    </div>
  )
}

下表显示了基于 URL 将渲染哪个组件

URL 路径组件
/<Index>
/a<PathlessLayout><A>
/b<PathlessLayout><B>

由于 TanStack Router 支持混合平面和目录路由,因此您也可以在目录中使用无路径布局路由来表达应用程序的路由

routes/
├── _pathlessLayout/
│   ├── route.tsx
│   ├── a.tsx
│   ├── b.tsx
routes/
├── _pathlessLayout/
│   ├── route.tsx
│   ├── a.tsx
│   ├── b.tsx

但是,与布局路由不同,由于无路径布局路由不基于 URL 路径段进行匹配,这意味着这些路由不支持动态路由段作为其路径的一部分,因此无法在 URL 中匹配。

这意味着您不能这样做

routes/
├── _$postId/ ❌
│   ├── ...
routes/
├── _$postId/ ❌
│   ├── ...

相反,您必须这样做

routes/
├── $postId/
├── _postPathlessLayout/ ✅
│   ├── ...
routes/
├── $postId/
├── _postPathlessLayout/ ✅
│   ├── ...

非嵌套路由

非嵌套路由可以通过在父文件路由段后添加_来创建,用于将路由从其父级取消嵌套并渲染其自己的组件树。

考虑以下平面路由树

routes/
├── posts.tsx
├── posts.$postId.tsx
├── posts_.$postId.edit.tsx
routes/
├── posts.tsx
├── posts.$postId.tsx
├── posts_.$postId.edit.tsx

下表显示了基于 URL 将渲染哪个组件

URL 路径组件
/posts<Posts>
/posts/123<Posts><Post postId="123">
/posts/123/edit<PostEditor postId="123">
  • posts.$postId.tsx路由像往常一样嵌套在posts.tsx路由下,并将渲染<Posts><Post>
  • posts_.$postId.edit.tsx路由不共享与其他路由相同的posts前缀,因此将被视为顶级路由,并将渲染<PostEditor>

无路径路由组目录

无路径路由组目录使用()作为一种将路由文件分组在一起的方式,而与其路径无关。它们纯粹是组织性的,不会以任何方式影响路由树或组件树。

routes/
├── index.tsx
├── (app)/
│   ├── dashboard.tsx
│   ├── settings.tsx
│   ├── users.tsx
├── (auth)/
│   ├── login.tsx
│   ├── register.tsx
routes/
├── index.tsx
├── (app)/
│   ├── dashboard.tsx
│   ├── settings.tsx
│   ├── users.tsx
├── (auth)/
│   ├── login.tsx
│   ├── register.tsx

在上面的示例中,appauth目录纯粹是组织性的,不会以任何方式影响路由树或组件树。它们用于将相关路由分组在一起,以便于导航和组织。

下表显示了基于 URL 将渲染哪个组件

URL 路径组件
/<Index>
/dashboard<Dashboard>
/settings<Settings>
/users<Users>
/login<Login>
/register<Register>

如您所见,appauth目录纯粹是组织性的,不会以任何方式影响路由树或组件树。

订阅 Bytes

您的每周 JavaScript 新闻。每周一免费发送给超过 100,000 名开发者。

Bytes

无垃圾邮件。随时取消订阅。