Daten laden

Das Laden von Daten ist ein häufiges Anliegen für Webanwendungen und steht in Beziehung zum Routing. Beim Laden einer Seite für Ihre App ist es ideal, wenn alle asynchronen Anforderungen der Seite so früh wie möglich und parallel abgerufen und erfüllt werden. Der Router ist der beste Ort, um diese asynchronen Abhängigkeiten zu koordinieren, da er normalerweise der einzige Ort in Ihrer App ist, der weiß, wohin sich Benutzer bewegen, bevor Inhalte gerendert werden.

Sie sind vielleicht vertraut mit getServerSideProps aus Next.js oder loadern aus Remix/React-Router. TanStack Router bietet ähnliche Funktionalität zum Vorladen/Laden von Assets pro Route parallel, was ein möglichst schnelles Rendern beim Abrufen über Suspense ermöglicht.

Über diese normalen Erwartungen eines Routers hinaus geht TanStack Router noch einen Schritt weiter und bietet **integrierte SWR-Zwischenspeicherung**, eine langfristige In-Memory-Zwischenspeicherungsschicht für Routen-Loader. Das bedeutet, dass Sie TanStack Router verwenden können, um Daten für Ihre Routen vorzuladen, damit diese sofort geladen werden, oder um Routendaten für zuvor besuchte Routen vorübergehend zu cachen, um sie später wiederzuverwenden.

Der Lebenszyklus des Routenladens

Jedes Mal, wenn eine URL-/Verlaufsaktualisierung erkannt wird, führt der Router die folgende Sequenz aus

  • Routenabgleich (von oben nach unten)
    • route.params.parse
    • route.validateSearch
  • Routen-Vorladung (seriell)
    • route.beforeLoad
    • route.onError
      • route.errorComponent / parentRoute.errorComponent / router.defaultErrorComponent
  • Routenladung (parallel)
    • route.component.preload?
    • route.loader
      • route.pendingComponent (Optional)
      • route.component
    • route.onError
      • route.errorComponent / parentRoute.errorComponent / router.defaultErrorComponent

Zum Router-Cache oder nicht zum Router-Cache?

Es ist sehr wahrscheinlich, dass der Router-Cache von TanStack gut für die meisten kleinen bis mittleren Anwendungen geeignet ist, aber es ist wichtig, die Kompromisse zu verstehen, die sich aus der Verwendung im Vergleich zu einer robusteren Caching-Lösung wie TanStack Query ergeben.

Vorteile des TanStack Router-Caches

  • Integriert, einfach zu bedienen, keine zusätzlichen Abhängigkeiten
  • Verarbeitet Deduplizierung, Vorladung, Laden, Stale-While-Revalidate, Hintergrund-Refetching pro Route
  • Grobe Invalidierung (alle Routen und den Cache auf einmal invalidieren)
  • Automatische Garbage Collection
  • Funktioniert gut für Apps, die wenig Daten zwischen Routen teilen
  • "Funktioniert einfach" für SSR

Nachteile des TanStack Router-Caches

  • Keine Persistenz-Adapter/Modelle
  • Kein gemeinsames Caching/Deduplizierung zwischen Routen
  • Keine integrierten Mutations-APIs (ein einfacher useMutation-Hook wird in vielen Beispielen bereitgestellt, was für viele Anwendungsfälle ausreichend sein kann)
  • Keine integrierten Cache-Level-optimistischen Update-APIs (Sie können immer noch ephemere Zustände von etwas wie einem useMutation-Hook verwenden, um dies auf Komponentenebene zu erreichen)

Tipp

Wenn Sie sofort wissen, dass Sie etwas Robusteres wie TanStack Query verwenden möchten oder müssen, springen Sie zum Leitfaden External Data Loading.

Verwendung des Router-Caches

Der Router-Cache ist integriert und so einfach wie die Rückgabe von Daten aus der loader-Funktion einer beliebigen Route. Lassen Sie uns lernen, wie!

Routen-loaders

Routen-loader-Funktionen werden aufgerufen, wenn ein Routen-Match geladen wird. Sie werden mit einem einzigen Parameter aufgerufen, der ein Objekt mit vielen hilfreichen Eigenschaften enthält. Wir werden diese gleich besprechen, aber zuerst sehen wir uns ein Beispiel für eine Routen-loader-Funktion an.

tsx
// routes/posts.tsx
export const Route = createFileRoute('/posts')({
  loader: () => fetchPosts(),
})
// routes/posts.tsx
export const Route = createFileRoute('/posts')({
  loader: () => fetchPosts(),
})

loader-Parameter

Die loader-Funktion empfängt ein einzelnes Objekt mit den folgenden Eigenschaften

  • abortController – Der AbortController der Route. Sein Signal wird abgebrochen, wenn die Route entladen wird oder wenn die Route nicht mehr relevant ist und die aktuelle Ausführung der loader-Funktion veraltet ist.
  • cause – Die Ursache des aktuellen Routen-Matches. Kann eine der folgenden sein
    • enter – Wenn die Route übereinstimmt und geladen wird, nachdem sie am vorherigen Ort nicht übereinstimmte.
    • preload – Wenn die Route vorgeladen wird.
    • stay – Wenn die Route übereinstimmt und geladen wird, nachdem sie am vorherigen Ort übereinstimmte.
  • context – Das Kontextobjekt der Route, das eine zusammengeführte Vereinigung von
    • Kontext der übergeordneten Route
    • Kontext dieser Route, wie er von der Option beforeLoad bereitgestellt wird
  • deps – Der Objektwert, der von der Funktion Route.loaderDeps zurückgegeben wird. Wenn Route.loaderDeps nicht definiert ist, wird stattdessen ein leeres Objekt bereitgestellt.
  • location – Der aktuelle Standort
  • params – Die Pfadparameter der Route
  • parentMatchPromisePromise<RouteMatch> (undefined für die Root-Route)
  • preload – Boolean, der true ist, wenn die Route vorgeladen anstatt geladen wird
  • route – Die Route selbst

Mit diesen Parametern können wir viele coole Dinge tun, aber zuerst schauen wir uns an, wie wir sie steuern und wann die loader-Funktion aufgerufen wird.

Daten aus loaders konsumieren

Um Daten aus einem loader zu konsumieren, verwenden Sie den Hook useLoaderData, der in Ihrem Route-Objekt definiert ist.

tsx
const posts = Route.useLoaderData()
const posts = Route.useLoaderData()

Wenn Sie keinen direkten Zugriff auf Ihr Routenobjekt haben (d. h. Sie befinden sich tief im Komponententeilbaum der aktuellen Route), können Sie getRouteApi verwenden, um auf denselben Hook (sowie die anderen Hooks im Route-Objekt) zuzugreifen. Dies sollte dem Importieren des Route-Objekts vorgezogen werden, da dies wahrscheinlich zu zirkulären Abhängigkeiten führen würde.

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

// in your component

const routeApi = getRouteApi('/posts')
const data = routeApi.useLoaderData()
import { getRouteApi } from '@tanstack/solid-router'

// in your component

const routeApi = getRouteApi('/posts')
const data = routeApi.useLoaderData()

Abhängigkeitsbasierte Stale-While-Revalidate-Zwischenspeicherung

TanStack Router bietet eine integrierte Stale-While-Revalidate-Zwischenspeicherungsschicht für Routen-Loader, die auf den Abhängigkeiten einer Route basiert

  • Der vollständig geparste Pfadname der Route
    • z.B. /posts/1 vs /posts/2
  • Zusätzliche Abhängigkeiten, die von der Option loaderDeps bereitgestellt werden
    • z.B. loaderDeps: ({ search: { pageIndex, pageSize } }) => ({ pageIndex, pageSize })

Anhand dieser Abhängigkeiten als Schlüssel speichert TanStack Router die von der loader-Funktion einer Route zurückgegebenen Daten und verwendet sie, um nachfolgende Anfragen für denselben Routen-Match zu erfüllen. Das bedeutet, dass, wenn die Daten einer Route bereits im Cache sind, sie sofort zurückgegeben werden und **möglicherweise** im Hintergrund neu abgerufen werden, abhängig von der "Frische" der Daten.

Schlüsseloptionen

Um Router-Abhängigkeiten und "Frische" zu steuern, bietet TanStack Router eine Fülle von Optionen zur Steuerung des Schlüssel- und Caching-Verhaltens Ihrer Routen-Loader. Lassen Sie uns diese in der Reihenfolge betrachten, in der Sie sie am wahrscheinlichsten verwenden werden.

  • routeOptions.loaderDeps
    • Eine Funktion, die Ihnen die Suchparameter für einen Router bereitstellt und ein Objekt von Abhängigkeiten für die Verwendung in Ihrer loader-Funktion zurückgibt. Wenn sich diese Abhängigkeiten von einer Navigation zur nächsten ändern, wird die Route neu geladen, unabhängig von staleTimes. Die Abhängigkeiten werden mittels eines tiefen Gleichheitsvergleichs verglichen.
  • routeOptions.staleTime
  • routerOptions.defaultStaleTime
    • Die Anzahl der Millisekunden, in denen die Daten einer Route als frisch gelten sollen, wenn versucht wird, sie zu laden.
  • routeOptions.preloadStaleTime
  • routerOptions.defaultPreloadStaleTime
    • Die Anzahl der Millisekunden, in denen die Daten einer Route als frisch gelten sollen, wenn versucht wird, sie vorzuladen.
  • routeOptions.gcTime
  • routerOptions.defaultGcTime
    • Die Anzahl der Millisekunden, in denen die Daten einer Route im Cache gespeichert werden sollen, bevor sie vom Garbage Collector bereinigt werden.
  • routeOptions.shouldReload
    • Eine Funktion, die die gleichen beforeLoad- und loaderContext-Parameter empfängt und einen booleschen Wert zurückgibt, der angibt, ob die Route neu geladen werden soll. Dies bietet eine weitere Kontrollebene darüber, wann eine Route neu geladen werden soll, über staleTime und loaderDeps hinaus und kann verwendet werden, um Muster ähnlich der Remix-Option shouldLoad zu implementieren.

⚠️ Einige wichtige Standardeinstellungen

  • Standardmäßig ist die staleTime auf 0 gesetzt, was bedeutet, dass die Daten der Route immer als veraltet betrachtet werden und immer im Hintergrund neu geladen werden, wenn die Route erneut übereinstimmt.
  • Standardmäßig gilt eine zuvor vorgeladene Route für **30 Sekunden** als frisch. Das bedeutet, wenn eine Route vorgeladen und dann innerhalb von 30 Sekunden nach dem letzten Loader-Ergebnis erneut vorgeladen wird, wird die zweite Vorladung ignoriert. Dies verhindert, dass unnötige Vorladungen zu häufig erfolgen. **Wenn eine Route normal geladen wird, wird die Standard-staleTime verwendet.**
  • Standardmäßig ist die gcTime auf **30 Minuten** gesetzt, was bedeutet, dass alle Routendaten, auf die 30 Minuten lang nicht zugegriffen wurde, vom Garbage Collector bereinigt und aus dem Cache entfernt werden.
  • router.invalidate() erzwingt das sofortige Neuladen der Loader für alle aktiven Routen und markiert die Daten aller gecachten Routen als veraltet.

Verwendung von loaderDeps zum Zugreifen auf Suchparameter

Stellen Sie sich eine /posts-Route vor, die eine Paginierung über Suchparameter offset und limit unterstützt. Damit der Cache diese Daten eindeutig speichern kann, müssen wir über die Funktion loaderDeps auf diese Suchparameter zugreifen. Indem wir sie explizit identifizieren, werden die einzelnen Routen-Matches für /posts mit unterschiedlichen offset und limit nicht vermischt!

Sobald wir diese Abhängigkeiten eingerichtet haben, wird die Route immer neu geladen, wenn sich die Abhängigkeiten ändern.

tsx
// /routes/posts.tsx
export const Route = createFileRoute('/posts')({
  loaderDeps: ({ search: { offset, limit } }) => ({ offset, limit }),
  loader: ({ deps: { offset, limit } }) =>
    fetchPosts({
      offset,
      limit,
    }),
})
// /routes/posts.tsx
export const Route = createFileRoute('/posts')({
  loaderDeps: ({ search: { offset, limit } }) => ({ offset, limit }),
  loader: ({ deps: { offset, limit } }) =>
    fetchPosts({
      offset,
      limit,
    }),
})

Verwendung von staleTime zur Steuerung, wie lange Daten als frisch gelten

Standardmäßig ist die staleTime für Navigationen auf 0 ms (und 30 Sekunden für Vorladungen) eingestellt, was bedeutet, dass die Daten der Route immer als veraltet gelten und immer im Hintergrund neu geladen werden, wenn die Route übereinstimmt und zu ihr navigiert wird.

Dies ist eine gute Standardeinstellung für die meisten Anwendungsfälle, aber Sie stellen möglicherweise fest, dass einige Routendaten statischer oder potenziell teuer zu laden sind. In diesen Fällen können Sie die Option staleTime verwenden, um zu steuern, wie lange die Daten der Route für Navigationen als frisch gelten. Sehen wir uns ein Beispiel an.

tsx
// /routes/posts.tsx
export const Route = createFileRoute('/posts')({
  loader: () => fetchPosts(),
  // Consider the route's data fresh for 10 seconds
  staleTime: 10_000,
})
// /routes/posts.tsx
export const Route = createFileRoute('/posts')({
  loader: () => fetchPosts(),
  // Consider the route's data fresh for 10 seconds
  staleTime: 10_000,
})

Durch die Übergabe von 10_000 an die Option staleTime teilen wir dem Router mit, die Daten der Route für 10 Sekunden als frisch zu betrachten. Das bedeutet, wenn der Benutzer innerhalb von 10 Sekunden nach dem letzten Loader-Ergebnis von /about zu /posts navigiert, werden die Daten der Route nicht neu geladen. Wenn der Benutzer dann nach 10 Sekunden von /about zu /posts navigiert, werden die Daten der Route **im Hintergrund** neu geladen.

Deaktivieren der Stale-While-Revalidate-Zwischenspeicherung

Um die Stale-While-Revalidate-Zwischenspeicherung für eine Route zu deaktivieren, setzen Sie die Option staleTime auf Infinity.

tsx
// /routes/posts.tsx
export const Route = createFileRoute('/posts')({
  loader: () => fetchPosts(),
  staleTime: Infinity,
})
// /routes/posts.tsx
export const Route = createFileRoute('/posts')({
  loader: () => fetchPosts(),
  staleTime: Infinity,
})

Sie können dies sogar für alle Routen deaktivieren, indem Sie die Option defaultStaleTime im Router setzen.

tsx
const router = createRouter({
  routeTree,
  defaultStaleTime: Infinity,
})
const router = createRouter({
  routeTree,
  defaultStaleTime: Infinity,
})

Verwendung von shouldReload und gcTime zum Opt-out von der Zwischenspeicherung

Ähnlich wie die Standardfunktionalität von Remix möchten Sie vielleicht eine Route so konfigurieren, dass sie nur beim Eintritt oder bei Änderung kritischer Loader-Abhängigkeiten geladen wird. Sie können dies tun, indem Sie die Option gcTime in Kombination mit der Option shouldReload verwenden, die entweder einen boolean oder eine Funktion akzeptiert, die die gleichen beforeLoad- und loaderContext-Parameter empfängt und einen booleschen Wert zurückgibt, der angibt, ob die Route neu geladen werden soll.

tsx
// /routes/posts.tsx
export const Route = createFileRoute('/posts')({
  loaderDeps: ({ search: { offset, limit } }) => ({ offset, limit }),
  loader: ({ deps }) => fetchPosts(deps),
  // Do not cache this route's data after it's unloaded
  gcTime: 0,
  // Only reload the route when the user navigates to it or when deps change
  shouldReload: false,
})
// /routes/posts.tsx
export const Route = createFileRoute('/posts')({
  loaderDeps: ({ search: { offset, limit } }) => ({ offset, limit }),
  loader: ({ deps }) => fetchPosts(deps),
  // Do not cache this route's data after it's unloaded
  gcTime: 0,
  // Only reload the route when the user navigates to it or when deps change
  shouldReload: false,
})

Opt-out von der Zwischenspeicherung bei gleichzeitiger Vorladung

Auch wenn Sie sich von der kurzfristigen Zwischenspeicherung für Ihre Routendaten abmelden, können Sie dennoch von der Vorladung profitieren! Mit der obigen Konfiguration funktioniert die Vorladung weiterhin "einfach" mit der Standard-preloadGcTime. Das bedeutet, wenn eine Route vorgeladen und dann navigiert wird, gelten die Daten der Route als frisch und werden nicht neu geladen.

Um sich von der Vorladung abzumelden, schalten Sie sie nicht über die Optionen routerOptions.defaultPreload oder routeOptions.preload ein.

Weiterleiten aller Loader-Ereignisse an einen externen Cache

Wir behandeln diesen Anwendungsfall auf der Seite External Data Loading, aber wenn Sie einen externen Cache wie TanStack Query verwenden möchten, können Sie dies tun, indem Sie alle Loader-Ereignisse an Ihren externen Cache weiterleiten. Solange Sie die Standardeinstellungen verwenden, besteht die einzige Änderung, die Sie vornehmen müssen, darin, die Option defaultPreloadStaleTime für den Router auf 0 zu setzen.

tsx
const router = createRouter({
  routeTree,
  defaultPreloadStaleTime: 0,
})
const router = createRouter({
  routeTree,
  defaultPreloadStaleTime: 0,
})

Dies stellt sicher, dass jedes Vorlade-, Lade- und Neuladeereignis Ihre loader-Funktionen auslöst, die dann von Ihrem externen Cache behandelt und dedupliziert werden können.

Verwendung des Router-Kontexts

Das Argument context, das an die loader-Funktion übergeben wird, ist ein Objekt, das eine zusammengeführte Vereinigung von

  • Kontext der übergeordneten Route
  • Kontext dieser Route, wie er von der Option beforeLoad bereitgestellt wird

Ganz oben im Router können Sie einen initialen Kontext über die Option context an den Router übergeben. Dieser Kontext ist für alle Routen im Router verfügbar und wird von jeder Route beim Abgleichen kopiert und erweitert. Dies geschieht, indem ein Kontext über die Option beforeLoad an eine Route übergeben wird. Dieser Kontext ist für alle untergeordneten Routen der Route verfügbar. Der resultierende Kontext ist für die loader-Funktion der Route verfügbar.

In diesem Beispiel erstellen wir eine Funktion in unserem Routenkontext, um Beiträge abzurufen, und verwenden sie dann in unserer loader-Funktion.

🧠 Kontext ist ein mächtiges Werkzeug für Dependency Injection. Sie können ihn verwenden, um Services, Hooks und andere Objekte in Ihren Router und Ihre Routen zu injizieren. Sie können auch Daten über den Routenbaum an jeder Route additiv weitergeben, indem Sie die Option beforeLoad einer Route verwenden.

  • /utils/fetchPosts.tsx
tsx
export const fetchPosts = async () => {
  const res = await fetch(`/api/posts?page=${pageIndex}`)
  if (!res.ok) throw new Error('Failed to fetch posts')
  return res.json()
}
export const fetchPosts = async () => {
  const res = await fetch(`/api/posts?page=${pageIndex}`)
  if (!res.ok) throw new Error('Failed to fetch posts')
  return res.json()
}
  • /routes/__root.tsx
tsx
import { createRootRouteWithContext } from '@tanstack/solid-router'

// Create a root route using the createRootRouteWithContext<{...}>() function and pass it whatever types you would like to be available in your router context.
export const Route = createRootRouteWithContext<{
  fetchPosts: typeof fetchPosts
}>()() // NOTE: the double call is on purpose, since createRootRouteWithContext is a factory ;)
import { createRootRouteWithContext } from '@tanstack/solid-router'

// Create a root route using the createRootRouteWithContext<{...}>() function and pass it whatever types you would like to be available in your router context.
export const Route = createRootRouteWithContext<{
  fetchPosts: typeof fetchPosts
}>()() // NOTE: the double call is on purpose, since createRootRouteWithContext is a factory ;)
  • /routes/posts.tsx
tsx
import { createFileRoute } from '@tanstack/solid-router'

// Notice how our postsRoute references context to get our fetchPosts function
// This can be a powerful tool for dependency injection across your router
// and routes.
export const Route = createFileRoute('/posts')({
  loader: ({ context: { fetchPosts } }) => fetchPosts(),
})
import { createFileRoute } from '@tanstack/solid-router'

// Notice how our postsRoute references context to get our fetchPosts function
// This can be a powerful tool for dependency injection across your router
// and routes.
export const Route = createFileRoute('/posts')({
  loader: ({ context: { fetchPosts } }) => fetchPosts(),
})
  • /router.tsx
tsx
import { routeTree } from './routeTree.gen'

// Use your routerContext to create a new router
// This will require that you fullfil the type requirements of the routerContext
const router = createRouter({
  routeTree,
  context: {
    // Supply the fetchPosts function to the router context
    fetchPosts,
  },
})
import { routeTree } from './routeTree.gen'

// Use your routerContext to create a new router
// This will require that you fullfil the type requirements of the routerContext
const router = createRouter({
  routeTree,
  context: {
    // Supply the fetchPosts function to the router context
    fetchPosts,
  },
})

Verwendung von Pfadparametern

Um Pfadparameter in Ihrer loader-Funktion zu verwenden, greifen Sie über die Eigenschaft params auf die Parameter der Funktion zu. Hier ist ein Beispiel.

tsx
// routes/posts.$postId.tsx
export const Route = createFileRoute('/posts/$postId')({
  loader: ({ params: { postId } }) => fetchPostById(postId),
})
// routes/posts.$postId.tsx
export const Route = createFileRoute('/posts/$postId')({
  loader: ({ params: { postId } }) => fetchPostById(postId),
})

Verwendung des Routenkontexts

Das Heruntergeben eines globalen Kontexts an Ihren Router ist großartig, aber was, wenn Sie einen Kontext bereitstellen möchten, der für eine bestimmte Route spezifisch ist? Hier kommt die Option beforeLoad ins Spiel. Die Option beforeLoad ist eine Funktion, die unmittelbar vor dem Versuch, eine Route zu laden, ausgeführt wird und die gleichen Parameter wie loader empfängt. Neben ihrer Fähigkeit, potenzielle Übereinstimmungen umzuleiten, Loader-Anfragen zu blockieren usw., kann sie auch ein Objekt zurückgeben, das in den Kontext der Route integriert wird. Sehen wir uns ein Beispiel an, bei dem wir einige Daten über die Option beforeLoad in unseren Routenkontext injizieren.

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

export const Route = createFileRoute('/posts')({
  // Pass the fetchPosts function to the route context
  beforeLoad: () => ({
    fetchPosts: () => console.info('foo'),
  }),
  loader: ({ context: { fetchPosts } }) => {
    console.info(fetchPosts()) // 'foo'

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

export const Route = createFileRoute('/posts')({
  // Pass the fetchPosts function to the route context
  beforeLoad: () => ({
    fetchPosts: () => console.info('foo'),
  }),
  loader: ({ context: { fetchPosts } }) => {
    console.info(fetchPosts()) // 'foo'

    // ...
  },
})

Verwendung von Suchparametern in Loadern

❓ Aber warte mal, Tanner... wo sind meine Suchparameter?

Sie fragen sich vielleicht, warum search nicht direkt in den Parametern der loader-Funktion verfügbar ist. Wir haben es bewusst so gestaltet, um Ihnen zum Erfolg zu verhelfen. Lassen Sie uns sehen, warum.

  • Suchparameter, die in einer Loader-Funktion verwendet werden, sind ein sehr guter Indikator dafür, dass diese Suchparameter auch verwendet werden sollten, um die geladenen Daten eindeutig zu identifizieren. Zum Beispiel haben Sie möglicherweise eine Route, die einen Suchparameter wie pageIndex verwendet, der die im Routen-Match enthaltenen Daten eindeutig identifiziert. Oder stellen Sie sich eine /users/user-Route vor, die den Suchparameter userId verwendet, um einen bestimmten Benutzer in Ihrer Anwendung zu identifizieren. Sie könnten Ihre URL wie folgt modellieren: /users/user?userId=123. Das bedeutet, dass Ihre user-Route zusätzliche Hilfe benötigt, um einen bestimmten Benutzer zu identifizieren.
  • Der direkte Zugriff auf Suchparameter in einer Loader-Funktion kann zu Fehlern beim Caching und Vorladen führen, bei denen die geladenen Daten nicht eindeutig für den aktuellen URL-Pfadnamen und die Suchparameter sind. Sie könnten zum Beispiel Ihre /posts-Route bitten, die Ergebnisse von Seite 2 vorzuladen, aber ohne die Unterscheidung von Seiten in Ihrer Routenkonfiguration würden Sie Seite 2's Daten auf Ihrem /posts- oder ?page=1-Bildschirm abrufen, speichern und anzeigen, anstatt sie im Hintergrund vorzuladen!
  • Das Platzieren einer Schwelle zwischen Suchparametern und der Loader-Funktion ermöglicht es dem Router, Ihre Abhängigkeiten und Reaktivität zu verstehen.
tsx
// /routes/users.user.tsx
export const Route = createFileRoute('/users/user')({
  validateSearch: (search) =>
    search as {
      userId: string
    },
  loaderDeps: ({ search: { userId } }) => ({
    userId,
  }),
  loader: async ({ deps: { userId } }) => getUser(userId),
})
// /routes/users.user.tsx
export const Route = createFileRoute('/users/user')({
  validateSearch: (search) =>
    search as {
      userId: string
    },
  loaderDeps: ({ search: { userId } }) => ({
    userId,
  }),
  loader: async ({ deps: { userId } }) => getUser(userId),
})

Zugriff auf Suchparameter über routeOptions.loaderDeps

tsx
// /routes/posts.tsx
export const Route = createFileRoute('/posts')({
  // Use zod to validate and parse the search params
  validateSearch: z.object({
    offset: z.number().int().nonnegative().catch(0),
  }),
  // Pass the offset to your loader deps via the loaderDeps function
  loaderDeps: ({ search: { offset } }) => ({ offset }),
  // Use the offset from context in the loader function
  loader: async ({ deps: { offset } }) =>
    fetchPosts({
      offset,
    }),
})
// /routes/posts.tsx
export const Route = createFileRoute('/posts')({
  // Use zod to validate and parse the search params
  validateSearch: z.object({
    offset: z.number().int().nonnegative().catch(0),
  }),
  // Pass the offset to your loader deps via the loaderDeps function
  loaderDeps: ({ search: { offset } }) => ({ offset }),
  // Use the offset from context in the loader function
  loader: async ({ deps: { offset } }) =>
    fetchPosts({
      offset,
    }),
})

Verwendung des Abort-Signals

Die Eigenschaft abortController der loader-Funktion ist ein AbortController. Sein Signal wird abgebrochen, wenn die Route entladen wird oder wenn der loader-Aufruf veraltet ist. Dies ist nützlich zum Abbrechen von Netzwerkanfragen, wenn die Route entladen wird oder wenn sich die Parameter der Route ändern. Hier ist ein Beispiel, das es mit einem Fetch-Aufruf verwendet.

tsx
// routes/posts.tsx
export const Route = createFileRoute('/posts')({
  loader: ({ abortController }) =>
    fetchPosts({
      // Pass this to an underlying fetch call or anything that supports signals
      signal: abortController.signal,
    }),
})
// routes/posts.tsx
export const Route = createFileRoute('/posts')({
  loader: ({ abortController }) =>
    fetchPosts({
      // Pass this to an underlying fetch call or anything that supports signals
      signal: abortController.signal,
    }),
})

Verwendung des preload-Flags

Die Eigenschaft preload der loader-Funktion ist ein Boolean, der true ist, wenn die Route vorgeladen anstatt geladen wird. Einige Datenladebibliotheken behandeln die Vorladung möglicherweise anders als ein Standard-Fetch, sodass Sie preload an Ihre Datenladebibliothek übergeben oder sie verwenden möchten, um die entsprechende Datenladelogik auszuführen.

tsx
// routes/posts.tsx
export const Route = createFileRoute('/posts')({
  loader: async ({ preload }) =>
    fetchPosts({
      maxAge: preload ? 10_000 : 0, // Preloads should hang around a bit longer
    }),
})
// routes/posts.tsx
export const Route = createFileRoute('/posts')({
  loader: async ({ preload }) =>
    fetchPosts({
      maxAge: preload ? 10_000 : 0, // Preloads should hang around a bit longer
    }),
})

Umgang mit langsamen Loadern

Idealerweise können die meisten Routen-Loader ihre Daten innerhalb eines kurzen Moments auflösen, wodurch die Notwendigkeit entfällt, einen Platzhalter-Spinner zu rendern, und sich einfach auf Suspense verlassen, um die nächste Route zu rendern, wenn sie vollständig bereit ist. Wenn kritische Daten, die zum Rendern der Komponente einer Route benötigt werden, jedoch langsam sind, haben Sie 2 Optionen

  • Teilen Sie Ihre schnellen und langsamen Daten in separate Promises auf und defern Sie die langsamen Daten, bis die schnellen Daten geladen sind (siehe Leitfaden Deferred Data Loading).
  • Zeigen Sie eine ausstehende Komponente nach einem optimistischen Suspense-Schwellenwert an, bis alle Daten bereit sind (siehe unten).

Anzeigen einer ausstehenden Komponente

Standardmäßig zeigt TanStack Router eine ausstehende Komponente für Loader an, die länger als 1 Sekunde zur Auflösung benötigen. Dies ist ein optimistischer Schwellenwert, der konfiguriert werden kann über

  • routeOptions.pendingMs oder
  • routerOptions.defaultPendingMs

Wenn der Schwellenwert für die ausstehende Zeit überschritten wird, rendert der Router die Option pendingComponent der Route, falls konfiguriert.

Vermeidung von Flash der ausstehenden Komponente

Wenn Sie eine ausstehende Komponente verwenden, ist das Letzte, was Sie wollen, dass Ihr ausstehender Zeit-Schwellenwert erreicht wird und Ihre Daten sofort danach aufgelöst werden, was zu einem abrupten Aufblitzen Ihrer ausstehenden Komponente führt. Um dies zu vermeiden, **zeigt TanStack Router standardmäßig Ihre ausstehende Komponente für mindestens 500 ms an**. Dies ist ein optimistischer Schwellenwert, der konfiguriert werden kann über

  • routeOptions.pendingMinMs oder
  • routerOptions.defaultPendingMinMs

Fehlerbehandlung

TanStack Router bietet mehrere Möglichkeiten, Fehler zu behandeln, die während des Routenladens auftreten. Lassen Sie uns diese betrachten.

Fehlerbehandlung mit routeOptions.onError

Die Option routeOptions.onError ist eine Funktion, die aufgerufen wird, wenn während des Routenladens ein Fehler auftritt.

tsx
// routes/posts.tsx
export const Route = createFileRoute('/posts')({
  loader: () => fetchPosts(),
  onError: ({ error }) => {
    // Log the error
    console.error(error)
  },
})
// routes/posts.tsx
export const Route = createFileRoute('/posts')({
  loader: () => fetchPosts(),
  onError: ({ error }) => {
    // Log the error
    console.error(error)
  },
})

Fehlerbehandlung mit routeOptions.onCatch

Die Option routeOptions.onCatch ist eine Funktion, die aufgerufen wird, wenn ein Fehler vom CatchBoundary des Routers abgefangen wurde.

tsx
// routes/posts.tsx
export const Route = createFileRoute('/posts')({
  onCatch: ({ error, errorInfo }) => {
    // Log the error
    console.error(error)
  },
})
// routes/posts.tsx
export const Route = createFileRoute('/posts')({
  onCatch: ({ error, errorInfo }) => {
    // Log the error
    console.error(error)
  },
})

Fehlerbehandlung mit routeOptions.errorComponent

Die Option routeOptions.errorComponent ist eine Komponente, die gerendert wird, wenn während des Routenladens oder Rendering-Lebenszyklus ein Fehler auftritt. Sie wird mit den folgenden Props gerendert

  • error – Der aufgetretene Fehler
  • reset – Eine Funktion zum Zurücksetzen des internen CatchBoundary
tsx
// routes/posts.tsx
export const Route = createFileRoute('/posts')({
  loader: () => fetchPosts(),
  errorComponent: ({ error }) => {
    // Render an error message
    return <div>{error.message}</div>
  },
})
// routes/posts.tsx
export const Route = createFileRoute('/posts')({
  loader: () => fetchPosts(),
  errorComponent: ({ error }) => {
    // Render an error message
    return <div>{error.message}</div>
  },
})

Die Funktion reset kann verwendet werden, um dem Benutzer zu ermöglichen, das erneute Rendern der normalen Kinder des Fehlergrenzbereichs zu versuchen.

tsx
// routes/posts.tsx
export const Route = createFileRoute('/posts')({
  loader: () => fetchPosts(),
  errorComponent: ({ error, reset }) => {
    return (
      <div>
        {error.message}
        <button
          onClick={() => {
            // Reset the router error boundary
            reset()
          }}
        >
          retry
        </button>
      </div>
    )
  },
})
// routes/posts.tsx
export const Route = createFileRoute('/posts')({
  loader: () => fetchPosts(),
  errorComponent: ({ error, reset }) => {
    return (
      <div>
        {error.message}
        <button
          onClick={() => {
            // Reset the router error boundary
            reset()
          }}
        >
          retry
        </button>
      </div>
    )
  },
})

Wenn der Fehler das Ergebnis eines Routenladens war, sollten Sie stattdessen router.invalidate() aufrufen, was sowohl ein Router-Neuladen als auch ein Zurücksetzen des Fehlergrenzbereichs koordiniert.

tsx
// routes/posts.tsx
export const Route = createFileRoute('/posts')({
  loader: () => fetchPosts(),
  errorComponent: ({ error, reset }) => {
    const router = useRouter()

    return (
      <div>
        {error.message}
        <button
          onClick={() => {
            // Invalidate the route to reload the loader, which will also reset the error boundary
            router.invalidate()
          }}
        >
          retry
        </button>
      </div>
    )
  },
})
// routes/posts.tsx
export const Route = createFileRoute('/posts')({
  loader: () => fetchPosts(),
  errorComponent: ({ error, reset }) => {
    const router = useRouter()

    return (
      <div>
        {error.message}
        <button
          onClick={() => {
            // Invalidate the route to reload the loader, which will also reset the error boundary
            router.invalidate()
          }}
        >
          retry
        </button>
      </div>
    )
  },
})

Verwendung der Standard-ErrorComponent

TanStack Router bietet eine Standard-ErrorComponent, die gerendert wird, wenn während des Routenladens oder Rendering-Lebenszyklus ein Fehler auftritt. Wenn Sie sich entscheiden, die Fehlerkomponenten Ihrer Routen zu überschreiben, ist es dennoch ratsam, immer auf die Darstellung nicht abgefangener Fehler mit der Standard-ErrorComponent zurückzufallen.

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

export const Route = createFileRoute('/posts')({
  loader: () => fetchPosts(),
  errorComponent: ({ error }) => {
    if (error instanceof MyCustomError) {
      // Render a custom error message
      return <div>{error.message}</div>
    }

    // Fallback to the default ErrorComponent
    return <ErrorComponent error={error} />
  },
})
// routes/posts.tsx
import { createFileRoute, ErrorComponent } from '@tanstack/solid-router'

export const Route = createFileRoute('/posts')({
  loader: () => fetchPosts(),
  errorComponent: ({ error }) => {
    if (error instanceof MyCustomError) {
      // Render a custom error message
      return <div>{error.message}</div>
    }

    // Fallback to the default ErrorComponent
    return <ErrorComponent error={error} />
  },
})
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.