受保护的路由

身份验证是 Web 应用程序极其常见的需求。在本指南中,我们将介绍如何使用 TanStack Router 构建受保护的路由,以及当用户尝试访问它们时如何重定向到登录页面。

route.beforeLoad 选项

route.beforeLoad 选项允许您指定一个函数,该函数将在路由加载之前被调用。它接收与 route.loader 函数相同的所有参数。这是检查用户是否已通过身份验证的好地方,如果他们未通过身份验证,则将他们重定向到登录页面。

beforeLoad 函数相对于其他路由加载函数按相对顺序运行

  • 路由匹配(自顶向下)
    • route.params.parse
    • route.validateSearch
  • 路由加载(包括预加载)
    • route.beforeLoad
    • route.onError
  • 路由加载(并行)
    • route.component.preload?
    • route.load

重要的是要知道,路由的 beforeLoad 函数在其任何子路由的 beforeLoad 函数之前调用。它本质上是路由及其所有子路由的中间件函数。

如果在 beforeLoad 中抛出错误,则其任何子路由都不会尝试加载.

重定向

虽然不是必需的,但某些身份验证流程需要重定向到登录页面。为此,您可以从 beforeLoad 中抛出 redirect()

tsx
// src/routes/_authenticated.tsx
export const Route = createFileRoute('/_authenticated')({
  beforeLoad: async ({ location }) => {
    if (!isAuthenticated()) {
      throw redirect({
        to: '/login',
        search: {
          // Use the current location to power a redirect after login
          // (Do not use `router.state.resolvedLocation` as it can
          // potentially lag behind the actual current location)
          redirect: location.href,
        },
      })
    }
  },
})
// src/routes/_authenticated.tsx
export const Route = createFileRoute('/_authenticated')({
  beforeLoad: async ({ location }) => {
    if (!isAuthenticated()) {
      throw redirect({
        to: '/login',
        search: {
          // Use the current location to power a redirect after login
          // (Do not use `router.state.resolvedLocation` as it can
          // potentially lag behind the actual current location)
          redirect: location.href,
        },
      })
    }
  },
})

提示

redirect() 函数接受与 navigate 函数相同的所有选项,因此如果您想替换当前历史记录条目而不是添加新条目,则可以传递诸如 replace: true 之类的选项。

一旦您验证了用户身份,常见的做法是将他们重定向回他们尝试访问的页面。为此,您可以利用我们在原始重定向中添加的 redirect 查询参数。由于我们将用它替换整个 URL,因此 router.history.pushrouter.navigate 更适合。

tsx
router.history.push(search.redirect)
router.history.push(search.redirect)

非重定向的身份验证

某些应用程序选择不将用户重定向到登录页面,而是将用户保留在同一页面上,并显示一个登录表单,该表单可以替换主要内容或通过模态框隐藏它。这也可以通过 TanStack Router 实现,只需短路渲染通常会渲染子路由的 <Outlet />

tsx
// src/routes/_authenticated.tsx
export const Route = createFileRoute('/_authenticated')({
  component: () => {
    if (!isAuthenticated()) {
      return <Login />
    }

    return <Outlet />
  },
})
// src/routes/_authenticated.tsx
export const Route = createFileRoute('/_authenticated')({
  component: () => {
    if (!isAuthenticated()) {
      return <Login />
    }

    return <Outlet />
  },
})

这使用户保持在同一页面上,但仍然允许您渲染登录表单。一旦用户通过身份验证,您只需渲染 <Outlet />,子路由将被渲染。

使用 React 上下文/钩子的身份验证

如果您的身份验证流程依赖于与 React 上下文和/或钩子的交互,您需要使用 router.context 选项将您的身份验证状态传递给 TanStack Router。

重要提示

React 钩子不应在 React 组件外部使用。如果您需要在 React 组件外部使用钩子,则需要在包裹 <RouterProvider /> 的组件中从钩子中提取返回的状态,然后将返回值传递给 TanStack Router。

我们将在 Router 上下文 部分详细介绍 router.context 选项。

这是一个示例,演示了如何在 TanStack Router 中使用 React 上下文和钩子来保护受保护的路由。请在 受保护的路由示例 中查看完整的可用设置。

  • src/routes/__root.tsx
tsx
import { createRootRouteWithContext } from '@tanstack/solid-router'

interface MyRouterContext {
  // The ReturnType of your useAuth hook or the value of your AuthContext
  auth: AuthState
}

export const Route = createRootRouteWithContext<MyRouterContext>()({
  component: () => <Outlet />,
})
import { createRootRouteWithContext } from '@tanstack/solid-router'

interface MyRouterContext {
  // The ReturnType of your useAuth hook or the value of your AuthContext
  auth: AuthState
}

export const Route = createRootRouteWithContext<MyRouterContext>()({
  component: () => <Outlet />,
})
  • src/router.tsx
tsx
import { createRouter } from '@tanstack/solid-router'

import { routeTree } from './routeTree.gen'

export const router = createRouter({
  routeTree,
  context: {
    // auth will initially be undefined
    // We'll be passing down the auth state from within a React component
    auth: undefined!,
  },
})
import { createRouter } from '@tanstack/solid-router'

import { routeTree } from './routeTree.gen'

export const router = createRouter({
  routeTree,
  context: {
    // auth will initially be undefined
    // We'll be passing down the auth state from within a React component
    auth: undefined!,
  },
})
  • src/App.tsx
tsx
import { RouterProvider } from '@tanstack/solid-router'

import { AuthProvider, useAuth } from './auth'

import { router } from './router'

function InnerApp() {
  const auth = useAuth()
  return <RouterProvider router={router} context={{ auth }} />
}

function App() {
  return (
    <AuthProvider>
      <InnerApp />
    </AuthProvider>
  )
}
import { RouterProvider } from '@tanstack/solid-router'

import { AuthProvider, useAuth } from './auth'

import { router } from './router'

function InnerApp() {
  const auth = useAuth()
  return <RouterProvider router={router} context={{ auth }} />
}

function App() {
  return (
    <AuthProvider>
      <InnerApp />
    </AuthProvider>
  )
}

然后在受保护的路由中,您可以使用 beforeLoad 函数检查身份验证状态,如果用户未登录,则向您的 **登录路由** 抛出 redirect()

  • src/routes/dashboard.route.tsx
tsx
import { createFileRoute, redirect } from '@tanstack/solid-router'

export const Route = createFileRoute('/dashboard')({
  beforeLoad: ({ context, location }) => {
    if (!context.auth.isAuthenticated) {
      throw redirect({
        to: '/login',
        search: {
          redirect: location.href,
        },
      })
    }
  },
})
import { createFileRoute, redirect } from '@tanstack/solid-router'

export const Route = createFileRoute('/dashboard')({
  beforeLoad: ({ context, location }) => {
    if (!context.auth.isAuthenticated) {
      throw redirect({
        to: '/login',
        search: {
          redirect: location.href,
        },
      })
    }
  },
})

您还可以*选择性地*使用 非重定向的身份验证 方法来显示登录表单,而不是调用 **redirect**。

这种方法也可以与无路径或布局路由结合使用,以保护其父路由下的所有路由。

订阅 Bytes

您的每周 JavaScript 新闻。每周一免费发送给超过 10 万名开发者。

Bytes

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