提示
不建议大多数应用程序使用基于代码的路由。建议改用基于文件的路由。
基于代码的路由与基于文件的路由没有什么不同,它使用相同的路由树概念来组织、匹配和组合匹配的路由到组件树中。唯一的区别是,您使用代码而不是文件系统来组织路由。
让我们考虑路由树和嵌套指南中的相同路由树,并将其转换为基于代码的路由
这是基于文件的版本
routes/
├── __root.tsx
├── index.tsx
├── about.tsx
├── posts/
│ ├── index.tsx
│ ├── $postId.tsx
├── posts.$postId.edit.tsx
├── settings/
│ ├── profile.tsx
│ ├── notifications.tsx
├── _pathlessLayout.tsx
├── _pathlessLayout/
│ ├── route-a.tsx
├── ├── route-b.tsx
├── files/
│ ├── $.tsx
routes/
├── __root.tsx
├── index.tsx
├── about.tsx
├── posts/
│ ├── index.tsx
│ ├── $postId.tsx
├── posts.$postId.edit.tsx
├── settings/
│ ├── profile.tsx
│ ├── notifications.tsx
├── _pathlessLayout.tsx
├── _pathlessLayout/
│ ├── route-a.tsx
├── ├── route-b.tsx
├── files/
│ ├── $.tsx
这是基于代码的摘要版本
import { createRootRoute, createRoute } from '@tanstack/react-router'
const rootRoute = createRootRoute()
const indexRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/',
})
const aboutRoute = createRoute({
getParentRoute: () => rootRoute,
path: 'about',
})
const postsRoute = createRoute({
getParentRoute: () => rootRoute,
path: 'posts',
})
const postsIndexRoute = createRoute({
getParentRoute: () => postsRoute,
path: '/',
})
const postRoute = createRoute({
getParentRoute: () => postsRoute,
path: '$postId',
})
const postEditorRoute = createRoute({
getParentRoute: () => rootRoute,
path: 'posts/$postId/edit',
})
const settingsRoute = createRoute({
getParentRoute: () => rootRoute,
path: 'settings',
})
const profileRoute = createRoute({
getParentRoute: () => settingsRoute,
path: 'profile',
})
const notificationsRoute = createRoute({
getParentRoute: () => settingsRoute,
path: 'notifications',
})
const pathlessLayoutRoute = createRoute({
getParentRoute: () => rootRoute,
id: 'pathlessLayout',
})
const pathlessLayoutARoute = createRoute({
getParentRoute: () => pathlessLayoutRoute,
path: 'route-a',
})
const pathlessLayoutBRoute = createRoute({
getParentRoute: () => pathlessLayoutRoute,
path: 'route-b',
})
const filesRoute = createRoute({
getParentRoute: () => rootRoute,
path: 'files/$',
})
import { createRootRoute, createRoute } from '@tanstack/react-router'
const rootRoute = createRootRoute()
const indexRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/',
})
const aboutRoute = createRoute({
getParentRoute: () => rootRoute,
path: 'about',
})
const postsRoute = createRoute({
getParentRoute: () => rootRoute,
path: 'posts',
})
const postsIndexRoute = createRoute({
getParentRoute: () => postsRoute,
path: '/',
})
const postRoute = createRoute({
getParentRoute: () => postsRoute,
path: '$postId',
})
const postEditorRoute = createRoute({
getParentRoute: () => rootRoute,
path: 'posts/$postId/edit',
})
const settingsRoute = createRoute({
getParentRoute: () => rootRoute,
path: 'settings',
})
const profileRoute = createRoute({
getParentRoute: () => settingsRoute,
path: 'profile',
})
const notificationsRoute = createRoute({
getParentRoute: () => settingsRoute,
path: 'notifications',
})
const pathlessLayoutRoute = createRoute({
getParentRoute: () => rootRoute,
id: 'pathlessLayout',
})
const pathlessLayoutARoute = createRoute({
getParentRoute: () => pathlessLayoutRoute,
path: 'route-a',
})
const pathlessLayoutBRoute = createRoute({
getParentRoute: () => pathlessLayoutRoute,
path: 'route-b',
})
const filesRoute = createRoute({
getParentRoute: () => rootRoute,
path: 'files/$',
})
除了根路由之外的所有其他路由都使用 createRoute 函数配置
const route = createRoute({
getParentRoute: () => rootRoute,
path: '/posts',
component: PostsComponent,
})
const route = createRoute({
getParentRoute: () => rootRoute,
path: '/posts',
component: PostsComponent,
})
getParentRoute 选项是一个函数,它返回您正在创建的路由的父路由。
❓❓❓ “等等,你让我在创建的每个路由中都传递父路由?”
当然!传递父路由与 TanStack Router 的神奇类型安全息息相关。如果没有父路由,TypeScript 就不知道要为您的路由提供什么类型!
重要
对于每个非根路由或无路径布局路由的路由,都需要一个 path 选项。这是将与 URL 路径名匹配的路径,以确定路由是否匹配。
当在路由上配置路由 path 选项时,它会忽略开头和结尾的斜杠(不包括“索引”路由路径 /)。如果您愿意,可以包含它们,但它们将被 TanStack Router 内部规范化。以下是有效路径及其规范化后的表格
路径 | 规范化路径 |
---|---|
/ | / |
/about | about |
about/ | about |
about | about |
$ | $ |
/$ | $ |
/$/ | $ |
在代码中构建路由树时,仅仅定义每个路由的父路由是不够的。您还必须通过将每个路由添加到其父路由的 children 数组来构建最终的路由树。这是因为路由树不会像基于文件的路由那样自动为您构建。
/* prettier-ignore */
const routeTree = rootRoute.addChildren([
indexRoute,
aboutRoute,
postsRoute.addChildren([
postsIndexRoute,
postRoute,
]),
postEditorRoute,
settingsRoute.addChildren([
profileRoute,
notificationsRoute,
]),
pathlessLayoutRoute.addChildren([
pathlessLayoutARoute,
pathlessLayoutBRoute,
]),
filesRoute.addChildren([
fileRoute,
]),
])
/* prettier-ignore-end */
/* prettier-ignore */
const routeTree = rootRoute.addChildren([
indexRoute,
aboutRoute,
postsRoute.addChildren([
postsIndexRoute,
postRoute,
]),
postEditorRoute,
settingsRoute.addChildren([
profileRoute,
notificationsRoute,
]),
pathlessLayoutRoute.addChildren([
pathlessLayoutARoute,
pathlessLayoutBRoute,
]),
filesRoute.addChildren([
fileRoute,
]),
])
/* prettier-ignore-end */
但在开始构建路由树之前,您需要了解基于代码路由的路由概念是如何工作的。
信不信由你,基于文件的路由实际上是基于代码路由的超集,它利用文件系统和一些代码生成抽象来自动生成您上面看到的这种结构。
我们假设您已经阅读了路由概念指南,并且熟悉每个主要概念
现在,让我们看看如何在代码中创建每种路由类型。
谢天谢地,在基于代码的路由中创建根路由与在基于文件的路由中创建根路由是相同的。调用 createRootRoute() 函数。
然而,与基于文件的路由不同,如果您不想,则无需导出根路由。当然不建议在一个文件中构建整个路由树和应用程序(尽管您可以,我们在示例中也这样做,以简洁地演示路由概念)。
// Standard root route
import { createRootRoute } from '@tanstack/react-router'
const rootRoute = createRootRoute()
// Root route with Context
import { createRootRouteWithContext } from '@tanstack/react-router'
import type { QueryClient } from '@tanstack/react-query'
export interface MyRouterContext {
queryClient: QueryClient
}
const rootRoute = createRootRouteWithContext<MyRouterContext>()
// Standard root route
import { createRootRoute } from '@tanstack/react-router'
const rootRoute = createRootRoute()
// Root route with Context
import { createRootRouteWithContext } from '@tanstack/react-router'
import type { QueryClient } from '@tanstack/react-query'
export interface MyRouterContext {
queryClient: QueryClient
}
const rootRoute = createRootRouteWithContext<MyRouterContext>()
要了解有关 TanStack Router 中上下文的更多信息,请参阅路由器上下文指南。
要创建基本路由,只需向 createRoute 函数提供一个普通的 path 字符串
const aboutRoute = createRoute({
getParentRoute: () => rootRoute,
path: 'about',
})
const aboutRoute = createRoute({
getParentRoute: () => rootRoute,
path: 'about',
})
看,就是这么简单! aboutRoute 将匹配 URL /about。
与基于文件的路由使用 index 文件名表示索引路由不同,基于代码的路由使用单个斜杠 / 表示索引路由。例如,我们上面示例路由树中的 posts.index.tsx 文件在基于代码的路由中表示如下
const postsRoute = createRoute({
getParentRoute: () => rootRoute,
path: 'posts',
})
const postsIndexRoute = createRoute({
getParentRoute: () => postsRoute,
// Notice the single slash `/` here
path: '/',
})
const postsRoute = createRoute({
getParentRoute: () => rootRoute,
path: 'posts',
})
const postsIndexRoute = createRoute({
getParentRoute: () => postsRoute,
// Notice the single slash `/` here
path: '/',
})
因此,postsIndexRoute 将匹配 URL /posts/ (或 /posts)。
动态路由段在基于代码的路由中与在基于文件的路由中完全相同。只需在路径的段前加上 $,它将被捕获到路由的 loader 或 component 的 params 对象中
const postIdRoute = createRoute({
getParentRoute: () => postsRoute,
path: '$postId',
// In a loader
loader: ({ params }) => fetchPost(params.postId),
// Or in a component
component: PostComponent,
})
function PostComponent() {
const { postId } = postIdRoute.useParams()
return <div>Post ID: {postId}</div>
}
const postIdRoute = createRoute({
getParentRoute: () => postsRoute,
path: '$postId',
// In a loader
loader: ({ params }) => fetchPost(params.postId),
// Or in a component
component: PostComponent,
})
function PostComponent() {
const { postId } = postIdRoute.useParams()
return <div>Post ID: {postId}</div>
}
提示
如果您的组件是代码拆分的,您可以使用 getRouteApi 函数,以避免导入 postIdRoute 配置来访问类型化的 useParams() 钩子。
正如预期的那样,splat/catch-all 路由在基于代码的路由中与在基于文件的路由中也以相同的方式工作。只需在路径的段前加上一个 $,它将被捕获到 params 对象中,键为 _splat
const filesRoute = createRoute({
getParentRoute: () => rootRoute,
path: 'files',
})
const fileRoute = createRoute({
getParentRoute: () => filesRoute,
path: '$',
})
const filesRoute = createRoute({
getParentRoute: () => rootRoute,
path: 'files',
})
const fileRoute = createRoute({
getParentRoute: () => filesRoute,
path: '$',
})
对于 URL /documents/hello-world,params 对象将如下所示
{
'_splat': 'documents/hello-world'
}
{
'_splat': 'documents/hello-world'
}
布局路由是将子路由包装在布局组件中的路由。在基于代码的路由中,您可以通过简单地将路由嵌套在另一个路由下创建布局路由
const postsRoute = createRoute({
getParentRoute: () => rootRoute,
path: 'posts',
component: PostsLayoutComponent, // The layout component
})
function PostsLayoutComponent() {
return (
<div>
<h1>Posts</h1>
<Outlet />
</div>
)
}
const postsIndexRoute = createRoute({
getParentRoute: () => postsRoute,
path: '/',
})
const postsCreateRoute = createRoute({
getParentRoute: () => postsRoute,
path: 'create',
})
const routeTree = rootRoute.addChildren([
// The postsRoute is the layout route
// Its children will be nested under the PostsLayoutComponent
postsRoute.addChildren([postsIndexRoute, postsCreateRoute]),
])
const postsRoute = createRoute({
getParentRoute: () => rootRoute,
path: 'posts',
component: PostsLayoutComponent, // The layout component
})
function PostsLayoutComponent() {
return (
<div>
<h1>Posts</h1>
<Outlet />
</div>
)
}
const postsIndexRoute = createRoute({
getParentRoute: () => postsRoute,
path: '/',
})
const postsCreateRoute = createRoute({
getParentRoute: () => postsRoute,
path: 'create',
})
const routeTree = rootRoute.addChildren([
// The postsRoute is the layout route
// Its children will be nested under the PostsLayoutComponent
postsRoute.addChildren([postsIndexRoute, postsCreateRoute]),
])
现在,postsIndexRoute 和 postsCreateRoute 都将在 PostsLayoutComponent 中渲染其内容
// URL: /posts
<PostsLayoutComponent>
<PostsIndexComponent />
</PostsLayoutComponent>
// URL: /posts/create
<PostsLayoutComponent>
<PostsCreateComponent />
</PostsLayoutComponent>
// URL: /posts
<PostsLayoutComponent>
<PostsIndexComponent />
</PostsLayoutComponent>
// URL: /posts/create
<PostsLayoutComponent>
<PostsCreateComponent />
</PostsLayoutComponent>
在基于文件的路由中,无路径布局路由以 _ 为前缀,但在基于代码的路由中,这只是一个具有 id 而不是 path 选项的路由。这是因为基于代码的路由不使用文件系统来组织路由,因此无需在路由前加上 _ 来表示它没有路径。
const pathlessLayoutRoute = createRoute({
getParentRoute: () => rootRoute,
id: 'pathlessLayout',
component: PathlessLayoutComponent,
})
function PathlessLayoutComponent() {
return (
<div>
<h1>Pathless Layout</h1>
<Outlet />
</div>
)
}
const pathlessLayoutARoute = createRoute({
getParentRoute: () => pathlessLayoutRoute,
path: 'route-a',
})
const pathlessLayoutBRoute = createRoute({
getParentRoute: () => pathlessLayoutRoute,
path: 'route-b',
})
const routeTree = rootRoute.addChildren([
// The pathless layout route has no path, only an id
// So its children will be nested under the pathless layout route
pathlessLayoutRoute.addChildren([pathlessLayoutARoute, pathlessLayoutBRoute]),
])
const pathlessLayoutRoute = createRoute({
getParentRoute: () => rootRoute,
id: 'pathlessLayout',
component: PathlessLayoutComponent,
})
function PathlessLayoutComponent() {
return (
<div>
<h1>Pathless Layout</h1>
<Outlet />
</div>
)
}
const pathlessLayoutARoute = createRoute({
getParentRoute: () => pathlessLayoutRoute,
path: 'route-a',
})
const pathlessLayoutBRoute = createRoute({
getParentRoute: () => pathlessLayoutRoute,
path: 'route-b',
})
const routeTree = rootRoute.addChildren([
// The pathless layout route has no path, only an id
// So its children will be nested under the pathless layout route
pathlessLayoutRoute.addChildren([pathlessLayoutARoute, pathlessLayoutBRoute]),
])
现在 /route-a 和 /route-b 都将在 PathlessLayoutComponent 中渲染其内容
// URL: /route-a
<PathlessLayoutComponent>
<RouteAComponent />
</PathlessLayoutComponent>
// URL: /route-b
<PathlessLayoutComponent>
<RouteBComponent />
</PathlessLayoutComponent>
// URL: /route-a
<PathlessLayoutComponent>
<RouteAComponent />
</PathlessLayoutComponent>
// URL: /route-b
<PathlessLayoutComponent>
<RouteBComponent />
</PathlessLayoutComponent>
在基于代码的路由中构建非嵌套路由不需要在路径中使用尾随 _,但需要您使用正确的路径和嵌套来构建路由和路由树。让我们考虑一下我们希望帖子编辑器不嵌套在帖子路由下的路由树
为此,我们需要为帖子编辑器构建一个单独的路由,并在 path 选项中包含从我们希望路由嵌套的根(在本例中为根)的整个路径
// The posts editor route is nested under the root route
const postEditorRoute = createRoute({
getParentRoute: () => rootRoute,
// The path includes the entire path we need to match
path: 'posts/$postId/edit',
})
const postsRoute = createRoute({
getParentRoute: () => rootRoute,
path: 'posts',
})
const postRoute = createRoute({
getParentRoute: () => postsRoute,
path: '$postId',
})
const routeTree = rootRoute.addChildren([
// The post editor route is nested under the root route
postEditorRoute,
postsRoute.addChildren([postRoute]),
])
// The posts editor route is nested under the root route
const postEditorRoute = createRoute({
getParentRoute: () => rootRoute,
// The path includes the entire path we need to match
path: 'posts/$postId/edit',
})
const postsRoute = createRoute({
getParentRoute: () => rootRoute,
path: 'posts',
})
const postRoute = createRoute({
getParentRoute: () => postsRoute,
path: '$postId',
})
const routeTree = rootRoute.addChildren([
// The post editor route is nested under the root route
postEditorRoute,
postsRoute.addChildren([postRoute]),
])
您的每周 JavaScript 资讯。每周一免费发送给超过 10 万开发者。