Router-Kontext

Der Router-Kontext von TanStack Router ist ein sehr mächtiges Werkzeug, das unter anderem für Dependency Injection verwendet werden kann. Passend benannt, wird der Router-Kontext über den Router weitergegeben und durch jede übereinstimmende Route nach unten. An jeder Route in der Hierarchie kann der Kontext modifiziert oder ergänzt werden. Hier sind einige Möglichkeiten, wie Sie den Router-Kontext praktisch nutzen könnten

  • Dependency Injection
    • Sie können Abhängigkeiten bereitstellen (z. B. eine Loader-Funktion, einen Daten-Fetching-Client, einen Mutationsdienst), auf die die Route und alle untergeordneten Routen zugreifen und die sie verwenden können, ohne sie direkt zu importieren oder zu erstellen.
  • Breadcrumbs
    • Während das Hauptkontextobjekt für jede Route beim Abstieg zusammengeführt wird, wird auch der eindeutige Kontext jeder Route gespeichert, wodurch es möglich ist, Breadcrumbs oder Methoden an den Kontext jeder Route anzuhängen.
  • Dynamische Meta-Tag-Verwaltung
    • Sie können jeder Route Metadaten hinzufügen und dann einen Meta-Tag-Manager verwenden, um die Metadaten auf der Seite dynamisch zu aktualisieren, während der Benutzer die Website navigiert.

Dies sind nur vorgeschlagene Verwendungszwecke des Router-Kontexts. Sie können ihn für alles verwenden, was Sie möchten!

Typisierter Router-Kontext

Wie alles andere ist der Root-Router-Kontext streng typisiert. Dieser Typ kann über die Option beforeLoad einer beliebigen Route erweitert werden, da er den Routenbaum hinunter zusammengeführt wird. Um den Typ des Root-Router-Kontexts einzuschränken, müssen Sie die Funktion createRootRouteWithContext<YourContextTypeHere>()(routeOptions) verwenden, um einen neuen Router-Kontext zu erstellen, anstatt die Funktion createRootRoute(), um Ihre Root-Route zu erstellen. Hier ist ein Beispiel

tsx
import {
  createRootRouteWithContext,
  createRouter,
} from '@tanstack/solid-router'

interface MyRouterContext {
  user: User
}

// Use the routerContext to create your root route
const rootRoute = createRootRouteWithContext<MyRouterContext>()({
  component: App,
})

const routeTree = rootRoute.addChildren([
  // ...
])

// Use the routerContext to create your router
const router = createRouter({
  routeTree,
})
import {
  createRootRouteWithContext,
  createRouter,
} from '@tanstack/solid-router'

interface MyRouterContext {
  user: User
}

// Use the routerContext to create your root route
const rootRoute = createRootRouteWithContext<MyRouterContext>()({
  component: App,
})

const routeTree = rootRoute.addChildren([
  // ...
])

// Use the routerContext to create your router
const router = createRouter({
  routeTree,
})

Übergabe des initialen Router-Kontexts

Der Router-Kontext wird bei der Instanziierung an den Router übergeben. Sie können den initialen Router-Kontext über die Option context an den Router übergeben

Tipp

Wenn Ihr Kontext erforderliche Eigenschaften hat, sehen Sie einen TypeScript-Fehler, wenn Sie diese nicht im initialen Router-Kontext übergeben. Wenn alle Ihre Kontext-Eigenschaften optional sind, sehen Sie keinen TypeScript-Fehler und die Übergabe des Kontexts ist optional. Wenn Sie keinen Router-Kontext übergeben, wird standardmäßig {} verwendet.

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

// Use the routerContext you created to create your router
const router = createRouter({
  routeTree,
  context: {
    user: {
      id: '123',
      name: 'John Doe',
    },
  },
})
import { createRouter } from '@tanstack/solid-router'

// Use the routerContext you created to create your router
const router = createRouter({
  routeTree,
  context: {
    user: {
      id: '123',
      name: 'John Doe',
    },
  },
})

Ungültigerklärung des Router-Kontexts

Wenn Sie den Kontextzustand, den Sie an den Router übergeben, ungültig machen müssen, können Sie die Methode invalidate aufrufen, um dem Router mitzuteilen, dass der Kontext neu berechnet werden soll. Dies ist nützlich, wenn Sie den Kontextzustand aktualisieren müssen und der Router den Kontext für alle Routen neu berechnen soll.

tsx
function useAuth() {
  const router = useRouter()
  const [user, setUser] = useState<User | null>(null)

  useEffect(() => {
    const unsubscribe = auth.onAuthStateChanged((user) => {
      setUser(user)
      router.invalidate()
    })

    return unsubscribe
  }, [])

  return user
}
function useAuth() {
  const router = useRouter()
  const [user, setUser] = useState<User | null>(null)

  useEffect(() => {
    const unsubscribe = auth.onAuthStateChanged((user) => {
      setUser(user)
      router.invalidate()
    })

    return unsubscribe
  }, [])

  return user
}

Verwendung des Router-Kontexts

Sobald Sie den Typ des Router-Kontexts definiert haben, können Sie ihn in Ihren Routendefinitionen verwenden

tsx
// src/routes/todos.tsx
export const Route = createFileRoute('/todos')({
  component: Todos,
  loader: ({ context }) => fetchTodosByUserId(context.user.id),
})
// src/routes/todos.tsx
export const Route = createFileRoute('/todos')({
  component: Todos,
  loader: ({ context }) => fetchTodosByUserId(context.user.id),
})

Sie können sogar Daten-Fetching- und Mutationsimplementierungen selbst injizieren! Tatsächlich wird dies dringend empfohlen 😜

Probieren wir dies mit einer einfachen Funktion zum Abrufen einiger Todos aus

tsx
const fetchTodosByUserId = async ({ userId }) => {
  const response = await fetch(`/api/todos?userId=${userId}`)
  const data = await response.json()
  return data
}

const router = createRouter({
  routeTree: rootRoute,
  context: {
    userId: '123',
    fetchTodosByUserId,
  },
})
const fetchTodosByUserId = async ({ userId }) => {
  const response = await fetch(`/api/todos?userId=${userId}`)
  const data = await response.json()
  return data
}

const router = createRouter({
  routeTree: rootRoute,
  context: {
    userId: '123',
    fetchTodosByUserId,
  },
})

Dann, in Ihrer Route

tsx
// src/routes/todos.tsx
export const Route = createFileRoute('/todos')({
  component: Todos,
  loader: ({ context }) => context.fetchTodosByUserId(context.userId),
})
// src/routes/todos.tsx
export const Route = createFileRoute('/todos')({
  component: Todos,
  loader: ({ context }) => context.fetchTodosByUserId(context.userId),
})

Wie wäre es mit einer externen Daten-Fetching-Bibliothek?

tsx
import {
  createRootRouteWithContext,
  createRouter,
} from '@tanstack/solid-router'

interface MyRouterContext {
  queryClient: QueryClient
}

const rootRoute = createRootRouteWithContext<MyRouterContext>()({
  component: App,
})

const queryClient = new QueryClient()

const router = createRouter({
  routeTree: rootRoute,
  context: {
    queryClient,
  },
})
import {
  createRootRouteWithContext,
  createRouter,
} from '@tanstack/solid-router'

interface MyRouterContext {
  queryClient: QueryClient
}

const rootRoute = createRootRouteWithContext<MyRouterContext>()({
  component: App,
})

const queryClient = new QueryClient()

const router = createRouter({
  routeTree: rootRoute,
  context: {
    queryClient,
  },
})

Dann, in Ihrer Route

tsx
// src/routes/todos.tsx
export const Route = createFileRoute('/todos')({
  component: Todos,
  loader: async ({ context }) => {
    await context.queryClient.ensureQueryData({
      queryKey: ['todos', { userId: user.id }],
      queryFn: fetchTodos,
    })
  },
})
// src/routes/todos.tsx
export const Route = createFileRoute('/todos')({
  component: Todos,
  loader: async ({ context }) => {
    await context.queryClient.ensureQueryData({
      queryKey: ['todos', { userId: user.id }],
      queryFn: fetchTodos,
    })
  },
})

Wie wäre es mit der Verwendung von React Context/Hooks?

Wenn Sie versuchen, React Context oder Hooks in den Funktionen beforeLoad oder loader Ihrer Route zu verwenden, ist es wichtig, sich an die Rules of Hooks von React zu erinnern. Sie können Hooks nicht in einer Nicht-React-Funktion verwenden, daher können Sie Hooks nicht in Ihren beforeLoad oder loader Funktionen verwenden.

Wie können wir also React Context oder Hooks in den Funktionen beforeLoad oder loader unserer Route verwenden? Wir können den Router-Kontext verwenden, um den React Context oder Hooks an die Funktionen beforeLoad oder loader unserer Route weiterzugeben.

Betrachten wir die Einrichtung eines Beispiels, bei dem wir einen Hook useNetworkStrength an die loader-Funktion unserer Route weitergeben

  • src/routes/__root.tsx
tsx
// First, make sure the context for the root route is typed
import { createRootRouteWithContext } from '@tanstack/solid-router'
import { useNetworkStrength } from '@/hooks/useNetworkStrength'

interface MyRouterContext {
  networkStrength: ReturnType<typeof useNetworkStrength>
}

export const Route = createRootRouteWithContext<MyRouterContext>()({
  component: App,
})
// First, make sure the context for the root route is typed
import { createRootRouteWithContext } from '@tanstack/solid-router'
import { useNetworkStrength } from '@/hooks/useNetworkStrength'

interface MyRouterContext {
  networkStrength: ReturnType<typeof useNetworkStrength>
}

export const Route = createRootRouteWithContext<MyRouterContext>()({
  component: App,
})

In diesem Beispiel würden wir den Hook vor dem Rendern des Routers mit dem <RouterProvider /> instanziieren. Auf diese Weise würde der Hook in React-Land aufgerufen und somit die Rules of Hooks eingehalten.

  • src/router.tsx
tsx
import { createRouter } from '@tanstack/solid-router'

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

export const router = createRouter({
  routeTree,
  context: {
    networkStrength: undefined!, // We'll set this in React-land
  },
})
import { createRouter } from '@tanstack/solid-router'

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

export const router = createRouter({
  routeTree,
  context: {
    networkStrength: undefined!, // We'll set this in React-land
  },
})
  • src/main.tsx
tsx
import { RouterProvider } from '@tanstack/solid-router'
import { router } from './router'

import { useNetworkStrength } from '@/hooks/useNetworkStrength'

function App() {
  const networkStrength = useNetworkStrength()
  // Inject the returned value from the hook into the router context
  return <RouterProvider router={router} context={{ networkStrength }} />
}

// ...
import { RouterProvider } from '@tanstack/solid-router'
import { router } from './router'

import { useNetworkStrength } from '@/hooks/useNetworkStrength'

function App() {
  const networkStrength = useNetworkStrength()
  // Inject the returned value from the hook into the router context
  return <RouterProvider router={router} context={{ networkStrength }} />
}

// ...

In der loader-Funktion unserer Route können wir nun auf den Hook networkStrength aus dem Router-Kontext zugreifen

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

export const Route = createFileRoute('/posts')({
  component: Posts,
  loader: ({ context }) => {
    if (context.networkStrength === 'STRONG') {
      // Do something
    }
  },
})
import { createFileRoute } from '@tanstack/solid-router'

export const Route = createFileRoute('/posts')({
  component: Posts,
  loader: ({ context }) => {
    if (context.networkStrength === 'STRONG') {
      // Do something
    }
  },
})

Ändern des Router-Kontexts

Der Router-Kontext wird über den Routenbaum nach unten weitergegeben und bei jeder Route zusammengeführt. Das bedeutet, dass Sie den Kontext bei jeder Route modifizieren können und die Modifikationen für alle untergeordneten Routen verfügbar sind. Hier ist ein Beispiel

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

interface MyRouterContext {
  foo: boolean
}

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

interface MyRouterContext {
  foo: boolean
}

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

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

const router = createRouter({
  routeTree,
  context: {
    foo: true,
  },
})
import { createRouter } from '@tanstack/solid-router'

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

const router = createRouter({
  routeTree,
  context: {
    foo: true,
  },
})
  • src/routes/todos.tsx
tsx
import { createFileRoute } from '@tanstack/solid-router'

export const Route = createFileRoute('/todos')({
  component: Todos,
  beforeLoad: () => {
    return {
      bar: true,
    }
  },
  loader: ({ context }) => {
    context.foo // true
    context.bar // true
  },
})
import { createFileRoute } from '@tanstack/solid-router'

export const Route = createFileRoute('/todos')({
  component: Todos,
  beforeLoad: () => {
    return {
      bar: true,
    }
  },
  loader: ({ context }) => {
    context.foo // true
    context.bar // true
  },
})

Verarbeitung akkumulierter Routen-Kontexte

Kontexte, insbesondere die isolierten Routen-context-Objekte, machen es trivial, die Routen-Kontextobjekte für alle übereinstimmenden Routen zu akkumulieren und zu verarbeiten. Hier ist ein Beispiel, bei dem wir alle übereinstimmenden Routen-Kontexte verwenden, um eine Breadcrumb-Navigation zu erstellen

tsx
// src/routes/__root.tsx
export const Route = createRootRoute({
  component: () => {
    const matches = useRouterState({ select: (s) => s.matches })

    const breadcrumbs = matches
      .filter((match) => match.context.getTitle)
      .map(({ pathname, context }) => {
        return {
          title: context.getTitle(),
          path: pathname,
        }
      })

    // ...
  },
})
// src/routes/__root.tsx
export const Route = createRootRoute({
  component: () => {
    const matches = useRouterState({ select: (s) => s.matches })

    const breadcrumbs = matches
      .filter((match) => match.context.getTitle)
      .map(({ pathname, context }) => {
        return {
          title: context.getTitle(),
          path: pathname,
        }
      })

    // ...
  },
})

Mit demselben Routen-Kontext könnten wir auch einen Titel-Tag für die <head>-Sektion unserer Seite generieren

tsx
// src/routes/__root.tsx
export const Route = createRootRoute({
  component: () => {
    const matches = useRouterState({ select: (s) => s.matches })

    const matchWithTitle = [...matches]
      .reverse()
      .find((d) => d.context.getTitle)

    const title = matchWithTitle?.context.getTitle() || 'My App'

    return (
      <html>
        <head>
          <title>{title}</title>
        </head>
        <body>{/* ... */}</body>
      </html>
    )
  },
})
// src/routes/__root.tsx
export const Route = createRootRoute({
  component: () => {
    const matches = useRouterState({ select: (s) => s.matches })

    const matchWithTitle = [...matches]
      .reverse()
      .find((d) => d.context.getTitle)

    const title = matchWithTitle?.context.getTitle() || 'My App'

    return (
      <html>
        <head>
          <title>{title}</title>
        </head>
        <body>{/* ... */}</body>
      </html>
    )
  },
})
Unsere Partner
Code Rabbit
Netlify
Neon
Clerk
Convex
Sentry
Bytes abonnieren

Ihre wöchentliche Dosis JavaScript-Nachrichten. Jeden Montag kostenlos an über 100.000 Entwickler geliefert.

Bytes

Kein Spam. Jederzeit kündbar.

Bytes abonnieren

Ihre wöchentliche Dosis JavaScript-Nachrichten. Jeden Montag kostenlos an über 100.000 Entwickler geliefert.

Bytes

Kein Spam. Jederzeit kündbar.