TanStack Router's Router-Kontext ist ein sehr mächtiges Werkzeug, das für Dependency Injection und viele andere Dinge verwendet werden kann. Wie der Name schon sagt, wird der Router-Kontext durch den Router und durch jede übereinstimmende Route weitergegeben. An jeder Route in der Hierarchie kann der Kontext modifiziert oder ergänzt werden. Hier sind einige praktische Anwendungsmöglichkeiten des Router-Kontexts:
Dies sind nur Vorschläge für die Verwendung des Router-Kontexts. Sie können ihn für alles verwenden, was Sie möchten!
Wie alles andere ist auch der Root-Router-Kontext streng typisiert. Dieser Typ kann über die beforeLoad-Option jeder Route erweitert werden, da er im Routen-Match-Baum nach unten gemergt 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/react-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/react-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 dem Router bei der Instanziierung übergeben. Sie können den initialen Router-Kontext über die context-Option 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, ist der Standardwert {}.
import { createRouter } from '@tanstack/react-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/react-router'
// Use the routerContext you created to create your router
const router = createRouter({
routeTree,
context: {
user: {
id: '123',
name: 'John Doe',
},
},
})
Wenn Sie den an den Router übergebenen Kontextzustand ungültig machen müssen, können Sie die Methode invalidate aufrufen, um dem Router mitzuteilen, den Kontext neu zu berechnen. Dies ist nützlich, wenn Sie den Kontextzustand aktualisieren und möchten, dass der Router den Kontext für alle Routen neu berechnet.
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
}
Nachdem 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 ist dies sehr zu empfehlen 😜
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/react-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/react-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,
})
},
})
Bei dem Versuch, React Context oder Hooks in den beforeLoad- oder loader-Funktionen Ihrer Route zu verwenden, ist es wichtig, die Hooks-Regeln von React zu beachten. 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 verwenden wir also React Context oder Hooks in den beforeLoad- oder loader-Funktionen unserer Route? Wir können den Router-Kontext verwenden, um den React Context oder Hooks an die beforeLoad- oder loader-Funktionen unserer Route weiterzugeben.
Betrachten wir die Einrichtung eines Beispiels, bei dem wir einen useNetworkStrength-Hook an die loader-Funktion unserer Route weitergeben.
// First, make sure the context for the root route is typed
import { createRootRouteWithContext } from '@tanstack/react-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/react-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 der <RouterProvider /> instanziieren. Auf diese Weise würde der Hook in React-Land aufgerufen und somit die Hooks-Regeln eingehalten.
import { createRouter } from '@tanstack/react-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/react-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/react-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/react-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 }} />
}
// ...
Nun können wir in der loader-Funktion unserer Route auf den networkStrength-Hook aus dem Router-Kontext zugreifen.
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/posts')({
component: Posts,
loader: ({ context }) => {
if (context.networkStrength === 'STRONG') {
// Do something
}
},
})
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/posts')({
component: Posts,
loader: ({ context }) => {
if (context.networkStrength === 'STRONG') {
// Do something
}
},
})
Der Router-Kontext wird den Routenbaum hinuntergegeben und an jeder Route zusammengeführt. Das bedeutet, dass Sie den Kontext an jeder Route modifizieren können und die Modifikationen für alle untergeordneten Routen verfügbar sind. Hier ist ein Beispiel
import { createRootRouteWithContext } from '@tanstack/react-router'
interface MyRouterContext {
foo: boolean
}
export const Route = createRootRouteWithContext<MyRouterContext>()({
component: App,
})
import { createRootRouteWithContext } from '@tanstack/react-router'
interface MyRouterContext {
foo: boolean
}
export const Route = createRootRouteWithContext<MyRouterContext>()({
component: App,
})
import { createRouter } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen'
const router = createRouter({
routeTree,
context: {
foo: true,
},
})
import { createRouter } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen'
const router = createRouter({
routeTree,
context: {
foo: true,
},
})
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/todos')({
component: Todos,
beforeLoad: () => {
return {
bar: true,
}
},
loader: ({ context }) => {
context.foo // true
context.bar // true
},
})
import { createFileRoute } from '@tanstack/react-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 Routenkontext-Objekte für alle übereinstimmenden Routen zu sammeln und zu verarbeiten. Hier ist ein Beispiel, bei dem wir alle übereinstimmenden Routenkontexte verwenden, um eine Breadcrumb-Leiste zu generieren.
// 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 Routenkontext könnten wir auch einen Titel-Tag für den <head> 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.