框架
版本

未找到错误

⚠️ 本页介绍处理未找到错误的新 notFound 函数和 notFoundComponent API。NotFoundRoute 路由已弃用,并将在未来的版本中移除。更多信息请参阅NotFoundRoute 迁移

概述

TanStack Router 中未找到错误有两种用途

  • 不匹配的路由路径:当路径不匹配任何已知路由匹配模式,**或者**当它部分匹配某个路由,但带有额外的路径段时
    • 当路径不匹配任何已知路由匹配模式时,路由器将自动抛出未找到错误。
    • 如果路由器的 notFoundMode 设置为 fuzzy,则具有 notFoundComponent 的最近父路由将处理此错误。如果路由器的 notFoundMode 设置为 root,则根路由将处理此错误。
    • 示例
      • 当不存在 /users 路由时,尝试访问 /users
      • 当路由树仅处理 /posts/$postId 时,尝试访问 /posts/1/edit
  • 缺失资源:当资源无法找到时,例如具有给定 ID 的帖子或任何不可用或不存在的异步数据
    • 当资源无法找到时,你,开发者必须抛出未找到错误。这可以在 beforeLoadloader 函数中使用 notFound 工具完成。
    • 将由具有 notFoundComponent 的最近父路由(当在 loader 中调用 notFound 时)或根路由处理。
    • 示例
      • 当 ID 为 1 的帖子不存在时,尝试访问 /posts/1
      • 当文档不存在时,尝试访问 /docs/path/to/document

在底层,这两种情况都使用相同的 notFound 函数和 notFoundComponent API 实现。

notFoundMode 选项

当 TanStack Router 遇到一个路径名不匹配任何已知路由模式,**或者**部分匹配一个路由模式但带有额外的尾随路径名段时,它将自动抛出未找到错误。

根据 notFoundMode 选项,路由器将以不同方式处理这些自动错误:

  • “模糊”模式(默认):路由器将智能地找到最接近的匹配路由,并显示 notFoundComponent
  • “根”模式:所有未找到错误都将由根路由的 notFoundComponent 处理,而与最近匹配的路由无关。

notFoundMode: 'fuzzy'

默认情况下,路由器的 notFoundMode 设置为 fuzzy,这表示如果路径名不匹配任何已知路由,路由器将尝试使用具有子路由/(出口)和已配置的未找到组件的最接近匹配路由。

❓ 为什么这是默认设置? 模糊匹配旨在尽可能保留父级布局,为用户提供更多上下文,以便根据他们认为会到达的位置导航到有用的位置。

通过以下标准找到最近的合适路由

  • 该路由必须具有子路由,因此必须有一个 Outlet 来渲染 notFoundComponent
  • 路由必须配置有 notFoundComponent,或者路由器必须配置有 defaultNotFoundComponent

例如,考虑以下路由树

  • __root__(已配置 notFoundComponent
    • posts(已配置 notFoundComponent
      • $postId(已配置 notFoundComponent

如果提供了路径 /posts/1/edit,将渲染以下组件结构

  • <Root>
    • <Posts>
      • <Posts.notFoundComponent>

posts 路由的 notFoundComponent 将被渲染,因为它是具有子组件(因此有出口)且配置了 notFoundComponent 的最近合适父路由

notFoundMode: 'root'

notFoundMode 设置为 root 时,所有未找到错误都将由根路由的 notFoundComponent 处理,而不是从最近的模糊匹配路由冒泡。

例如,考虑以下路由树

  • __root__(已配置 notFoundComponent
    • posts(已配置 notFoundComponent
      • $postId(已配置 notFoundComponent

如果提供了路径 /posts/1/edit,将渲染以下组件结构

  • <Root>
    • <Root.notFoundComponent>

__root__ 路由的 notFoundComponent 将被渲染,因为 notFoundMode 设置为 root

配置路由的 notFoundComponent

要处理两种类型的未找到错误,您可以将 notFoundComponent 附加到路由。当抛出未找到错误时,将渲染此组件。

例如,为 /settings 路由配置 notFoundComponent 以处理不存在的设置页面

tsx
export const Route = createFileRoute('/settings')({
  component: () => {
    return (
      <div>
        <p>Settings page</p>
        <Outlet />
      </div>
    )
  },
  notFoundComponent: () => {
    return <p>This setting page doesn't exist!</p>
  },
})
export const Route = createFileRoute('/settings')({
  component: () => {
    return (
      <div>
        <p>Settings page</p>
        <Outlet />
      </div>
    )
  },
  notFoundComponent: () => {
    return <p>This setting page doesn't exist!</p>
  },
})

或者为 /posts/$postId 路由配置 notFoundComponent 以处理不存在的帖子

tsx
export const Route = createFileRoute('/posts/$postId')({
  loader: async ({ params: { postId } }) => {
    const post = await getPost(postId)
    if (!post) throw notFound()
    return { post }
  },
  component: ({ post }) => {
    return (
      <div>
        <h1>{post.title}</h1>
        <p>{post.body}</p>
      </div>
    )
  },
  notFoundComponent: () => {
    return <p>Post not found!</p>
  },
})
export const Route = createFileRoute('/posts/$postId')({
  loader: async ({ params: { postId } }) => {
    const post = await getPost(postId)
    if (!post) throw notFound()
    return { post }
  },
  component: ({ post }) => {
    return (
      <div>
        <h1>{post.title}</h1>
        <p>{post.body}</p>
      </div>
    )
  },
  notFoundComponent: () => {
    return <p>Post not found!</p>
  },
})

默认的全局路由未找到处理

您可能希望为应用程序中具有子路由的每个路由提供一个默认的未找到组件。

为什么只针对有子路由的路由?叶子节点路由(没有子路由的路由)永远不会渲染 Outlet,因此无法处理未找到错误。

为此,将 defaultNotFoundComponent 传递给 createRouter 函数。

tsx
const router = createRouter({
  defaultNotFoundComponent: () => {
    return (
      <div>
        <p>Not found!</p>
        <Link to="/">Go home</Link>
      </div>
    )
  },
})
const router = createRouter({
  defaultNotFoundComponent: () => {
    return (
      <div>
        <p>Not found!</p>
        <Link to="/">Go home</Link>
      </div>
    )
  },
})

抛出你自己的 notFound 错误

您可以使用 notFound 函数在加载器方法和组件中手动抛出未找到错误。当您需要表示资源无法找到时,这非常有用。

notFound 函数的工作方式与 redirect 函数类似。要触发未找到错误,您可以抛出 notFound()

tsx
export const Route = createFileRoute('/posts/$postId')({
  loader: async ({ params: { postId } }) => {
    // Returns `null` if the post doesn't exist
    const post = await getPost(postId)
    if (!post) {
      throw notFound()
      // Alternatively, you can make the notFound function throw:
      // notFound({ throw: true })
    }
    // Post is guaranteed to be defined here because we threw an error
    return { post }
  },
})
export const Route = createFileRoute('/posts/$postId')({
  loader: async ({ params: { postId } }) => {
    // Returns `null` if the post doesn't exist
    const post = await getPost(postId)
    if (!post) {
      throw notFound()
      // Alternatively, you can make the notFound function throw:
      // notFound({ throw: true })
    }
    // Post is guaranteed to be defined here because we threw an error
    return { post }
  },
})

上述未找到错误将由具有 notFoundComponent 路由选项或 defaultNotFoundComponent 路由器选项的同一路由或最近的父路由处理。

如果找不到路由或任何合适的父路由来处理此错误,则根路由将使用 TanStack Router 极其基本(且故意不理想) 的默认未找到组件来处理,该组件仅渲染 <div>Not Found</div>。强烈建议您至少将一个 notFoundComponent 附加到根路由,或配置一个全局路由器 defaultNotFoundComponent 来处理未找到错误。

指定哪些路由处理未找到错误

有时您可能希望在特定的父路由上触发未找到错误,并绕过正常的未找到组件传播。为此,请在 notFound 函数的 route 选项中传入路由 ID。

tsx
// _pathlessLayout.tsx
export const Route = createFileRoute('/_pathlessLayout')({
  // This will render
  notFoundComponent: () => {
    return <p>Not found (in _pathlessLayout)</p>
  },
  component: () => {
    return (
      <div>
        <p>This is a pathless layout route!</p>
        <Outlet />
      </div>
    )
  },
})

// _pathlessLayout/route-a.tsx
export const Route = createFileRoute('/_pathless/route-a')({
  loader: async () => {
    // This will make LayoutRoute handle the not-found error
    throw notFound({ routeId: '/_pathlessLayout' })
    //                      ^^^^^^^^^ This will autocomplete from the registered router
  },
  // This WILL NOT render
  notFoundComponent: () => {
    return <p>Not found (in _pathlessLayout/route-a)</p>
  },
})
// _pathlessLayout.tsx
export const Route = createFileRoute('/_pathlessLayout')({
  // This will render
  notFoundComponent: () => {
    return <p>Not found (in _pathlessLayout)</p>
  },
  component: () => {
    return (
      <div>
        <p>This is a pathless layout route!</p>
        <Outlet />
      </div>
    )
  },
})

// _pathlessLayout/route-a.tsx
export const Route = createFileRoute('/_pathless/route-a')({
  loader: async () => {
    // This will make LayoutRoute handle the not-found error
    throw notFound({ routeId: '/_pathlessLayout' })
    //                      ^^^^^^^^^ This will autocomplete from the registered router
  },
  // This WILL NOT render
  notFoundComponent: () => {
    return <p>Not found (in _pathlessLayout/route-a)</p>
  },
})

手动定位到根路由

您还可以通过将导出的 rootRouteId 变量传递给 notFound 函数的 route 属性来定位根路由。

tsx
import { rootRouteId } from '@tanstack/react-router'

export const Route = createFileRoute('/posts/$postId')({
  loader: async ({ params: { postId } }) => {
    const post = await getPost(postId)
    if (!post) throw notFound({ routeId: rootRouteId })
    return { post }
  },
})
import { rootRouteId } from '@tanstack/react-router'

export const Route = createFileRoute('/posts/$postId')({
  loader: async ({ params: { postId } }) => {
    const post = await getPost(postId)
    if (!post) throw notFound({ routeId: rootRouteId })
    return { post }
  },
})

在组件中抛出未找到错误

您也可以在组件中抛出未找到错误。但是,建议在加载器方法中而不是组件中抛出未找到错误,以便正确类型化加载器数据并防止闪烁。

TanStack Router 暴露了一个类似于 CatchBoundaryCatchNotFound 组件,可用于捕获组件中的未找到错误并相应地显示 UI。

notFoundComponent 内部的数据加载

在数据加载方面,notFoundComponent 是一个特例。SomeRoute.useLoaderData 可能未定义,具体取决于您尝试访问的路由以及未找到错误抛出的位置。但是,Route.useParamsRoute.useSearchRoute.useRouteContext 等将返回一个已定义的值。

如果您需要将不完整的加载器数据传递给 notFoundComponent 通过 notFound 函数中的 data 选项传递数据,并在 notFoundComponent 中进行验证。

tsx
export const Route = createFileRoute('/posts/$postId')({
  loader: async ({ params: { postId } }) => {
    const post = await getPost(postId)
    if (!post)
      throw notFound({
        // Forward some data to the notFoundComponent
        // data: someIncompleteLoaderData
      })
    return { post }
  },
  // `data: unknown` is passed to the component via the `data` option when calling `notFound`
  notFoundComponent: ({ data }) => {
    // ❌ useLoaderData is not valid here: const { post } = Route.useLoaderData()

    // ✅:
    const { postId } = Route.useParams()
    const search = Route.useSearch()
    const context = Route.useRouteContext()

    return <p>Post with id {postId} not found!</p>
  },
})
export const Route = createFileRoute('/posts/$postId')({
  loader: async ({ params: { postId } }) => {
    const post = await getPost(postId)
    if (!post)
      throw notFound({
        // Forward some data to the notFoundComponent
        // data: someIncompleteLoaderData
      })
    return { post }
  },
  // `data: unknown` is passed to the component via the `data` option when calling `notFound`
  notFoundComponent: ({ data }) => {
    // ❌ useLoaderData is not valid here: const { post } = Route.useLoaderData()

    // ✅:
    const { postId } = Route.useParams()
    const search = Route.useSearch()
    const context = Route.useRouteContext()

    return <p>Post with id {postId} not found!</p>
  },
})

与 SSR 一起使用

更多信息请参见SSR 指南

NotFoundRoute 迁移

NotFoundRoute API 已弃用,取而代之的是 notFoundComponentNotFoundRoute API 将在未来的版本中移除。

在使用 NotFoundRoute 时,notFound 函数和 notFoundComponent 将不起作用。

主要区别在于

  • NotFoundRoute 是一个路由,需要其父路由上的 <Outlet> 才能渲染。notFoundComponent 是一个可以附加到任何路由的组件。
  • 使用 NotFoundRoute 时,不能使用布局。notFoundComponent 可以与布局一起使用。
  • 使用 notFoundComponent 时,路径匹配是严格的。这意味着如果您在 /post/$postId 有一个路由,如果您尝试访问 /post/1/2/3,将抛出未找到错误。使用 NotFoundRoute/post/1/2/3 将匹配 NotFoundRoute,并且只有在有 <Outlet> 时才会渲染它。

要从 NotFoundRoute 迁移到 notFoundComponent,您只需要做一些更改

tsx
// router.tsx
import { createRouter } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen.'
- import { notFoundRoute } from './notFoundRoute'  // [!code --]

export const router = createRouter({
  routeTree,
- notFoundRoute // [!code --]
})

// routes/__root.tsx
import { createRootRoute } from '@tanstack/react-router'

export const Route = createRootRoute({
  // ...
+ notFoundComponent: () => {  // [!code ++]
+   return <p>Not found!</p>  // [!code ++]
+ } // [!code ++]
})
// router.tsx
import { createRouter } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen.'
- import { notFoundRoute } from './notFoundRoute'  // [!code --]

export const router = createRouter({
  routeTree,
- notFoundRoute // [!code --]
})

// routes/__root.tsx
import { createRootRoute } from '@tanstack/react-router'

export const Route = createRootRoute({
  // ...
+ notFoundComponent: () => {  // [!code ++]
+   return <p>Not found!</p>  // [!code ++]
+ } // [!code ++]
})

重要更改

  • 根路由添加了 notFoundComponent,用于全局未找到处理。
    • 您还可以将 notFoundComponent 添加到路由树中的任何其他路由,以处理该特定路由的未找到错误。
  • notFoundComponent 不支持渲染 <Outlet>
我们的合作伙伴
Code Rabbit
Netlify
Neon
Clerk
Convex
Sentry
订阅 Bytes

您的每周 JavaScript 资讯。每周一免费发送给超过 10 万开发者。

Bytes

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

订阅 Bytes

您的每周 JavaScript 资讯。每周一免费发送给超过 10 万开发者。

Bytes

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