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
Dies sind nur vorgeschlagene Verwendungszwecke des Router-Kontexts. Sie können ihn für alles verwenden, was Sie möchten!
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
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,
})
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.
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',
},
},
})
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.
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
}
Sobald Sie den Typ des Router-Kontexts definiert haben, können Sie ihn in Ihren Routendefinitionen verwenden
// 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
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
// 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),
})
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
// 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,
})
},
})
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
// 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.
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
},
})
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
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
}
},
})
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
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,
})
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,
},
})
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
},
})
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
// 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
// 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>
)
},
})
Ihre wöchentliche Dosis JavaScript-Nachrichten. Jeden Montag kostenlos an über 100.000 Entwickler geliefert.