Externes Laden von Daten

Wichtig

Diese Anleitung richtet sich an externe State-Management-Bibliotheken und deren Integration mit TanStack Router für Data-Fetching, SSR, Hydrierung/Dehydrierung und Streaming. Wenn Sie die Standardanleitung für das Laden von Daten noch nicht gelesen haben, tun Sie dies bitte zuerst.

Speichern oder Koordinieren?

Während der Router von Haus aus sehr gut darin ist, die meisten Datenbedürfnisse zu speichern und zu verwalten, möchten Sie manchmal einfach etwas Robusteres!

Der Router ist als perfekter Koordinator für externe Data-Fetching- und Caching-Bibliotheken konzipiert. Das bedeutet, dass Sie jede beliebige Data-Fetching/Caching-Bibliothek verwenden können, und der Router wird das Laden Ihrer Daten so koordinieren, dass es mit der Navigation Ihrer Benutzer und den Erwartungen an die Aktualität übereinstimmt.

Welche Data-Fetching-Bibliotheken werden unterstützt?

Jede Data-Fetching-Bibliothek, die asynchrone Promises unterstützt, kann mit TanStack Router verwendet werden. Dazu gehören

Oder sogar...

Buchstäblich jede Bibliothek, die ein Promise zurückgeben und Daten lesen/schreiben kann, kann integriert werden.

Loader verwenden, um sicherzustellen, dass Daten geladen werden

Der einfachste Weg, eine externe Caching/Data-Bibliothek in den Router zu integrieren, ist die Verwendung von route.loaders, um sicherzustellen, dass die für eine Route benötigten Daten geladen wurden und zur Anzeige bereit sind.

⚠️ ABER WARUM? Es ist sehr wichtig, Ihre kritischen Renderdaten im Loader vorzuladen, und zwar aus mehreren Gründen

  • Keine "Flash of loading"-Zustände
  • Kein Wasserfall-Data-Fetching, verursacht durch komponentenbasiertes Fetching
  • Besser für SEO. Wenn Ihre Daten zur Renderzeit verfügbar sind, werden sie von Suchmaschinen indexiert.

Hier ist eine naive Darstellung (tun Sie dies nicht) der Verwendung der loader-Option einer Route, um den Cache für einige Daten zu füllen.

tsx
// src/routes/posts.tsx
let postsCache = []

export const Route = createFileRoute('/posts')({
  loader: async () => {
    postsCache = await fetchPosts()
  },
  component: () => {
    return (
      <div>
        {postsCache.map((post) => (
          <Post key={post.id} post={post} />
        ))}
      </div>
    )
  },
})
// src/routes/posts.tsx
let postsCache = []

export const Route = createFileRoute('/posts')({
  loader: async () => {
    postsCache = await fetchPosts()
  },
  component: () => {
    return (
      <div>
        {postsCache.map((post) => (
          <Post key={post.id} post={post} />
        ))}
      </div>
    )
  },
})

Dieses Beispiel ist offensichtlich fehlerhaft, veranschaulicht aber den Punkt, dass Sie die loader-Option einer Route verwenden können, um Ihren Cache mit Daten zu füllen. Werfen wir einen Blick auf ein realistischeres Beispiel mit TanStack Query.

  • Ersetzen Sie fetchPosts durch die Prefetching-API Ihrer bevorzugten Data-Fetching-Bibliothek
  • Ersetzen Sie postsCache durch die Read-or-Fetch-API oder den Hook Ihrer bevorzugten Data-Fetching-Bibliothek

Ein realistischeres Beispiel mit TanStack Query

Werfen wir einen Blick auf ein realistischeres Beispiel mit TanStack Query.

tsx
// src/routes/posts.tsx
const postsQueryOptions = queryOptions({
  queryKey: ['posts'],
  queryFn: () => fetchPosts(),
})

export const Route = createFileRoute('/posts')({
  // Use the `loader` option to ensure that the data is loaded
  loader: () => queryClient.ensureQueryData(postsQueryOptions),
  component: () => {
    // Read the data from the cache and subscribe to updates
    const {
      data: { posts },
    } = useSuspenseQuery(postsQueryOptions)

    return (
      <div>
        {posts.map((post) => (
          <Post key={post.id} post={post} />
        ))}
      </div>
    )
  },
})
// src/routes/posts.tsx
const postsQueryOptions = queryOptions({
  queryKey: ['posts'],
  queryFn: () => fetchPosts(),
})

export const Route = createFileRoute('/posts')({
  // Use the `loader` option to ensure that the data is loaded
  loader: () => queryClient.ensureQueryData(postsQueryOptions),
  component: () => {
    // Read the data from the cache and subscribe to updates
    const {
      data: { posts },
    } = useSuspenseQuery(postsQueryOptions)

    return (
      <div>
        {posts.map((post) => (
          <Post key={post.id} post={post} />
        ))}
      </div>
    )
  },
})

Fehlerbehandlung mit TanStack Query

Wenn ein Fehler auftritt, während Sie suspense mit TanStack Query verwenden, müssen Sie die Queries darauf hinweisen, dass Sie beim erneuten Rendern versuchen möchten, es erneut zu versuchen. Dies kann durch die Verwendung der reset-Funktion geschehen, die vom useQueryErrorResetBoundary-Hook bereitgestellt wird. Sie können diese Funktion in einem Effekt aufrufen, sobald die Fehlerkomponente gemountet ist. Dies stellt sicher, dass die Abfrage zurückgesetzt wird und versucht, die Daten erneut abzurufen, wenn die Routenkomponente erneut gerendert wird. Dies deckt auch Fälle ab, in denen Benutzer die Route verlassen, anstatt auf die retry-Schaltfläche zu klicken.

tsx
export const Route = createFileRoute('/')({
  loader: () => queryClient.ensureQueryData(postsQueryOptions),
  errorComponent: ({ error, reset }) => {
    const router = useRouter()
    const queryErrorResetBoundary = useQueryErrorResetBoundary()

    useEffect(() => {
      // Reset the query error boundary
      queryErrorResetBoundary.reset()
    }, [queryErrorResetBoundary])

    return (
      <div>
        {error.message}
        <button
          onClick={() => {
            // Invalidate the route to reload the loader, and reset any router error boundaries
            router.invalidate()
          }}
        >
          retry
        </button>
      </div>
    )
  },
})
export const Route = createFileRoute('/')({
  loader: () => queryClient.ensureQueryData(postsQueryOptions),
  errorComponent: ({ error, reset }) => {
    const router = useRouter()
    const queryErrorResetBoundary = useQueryErrorResetBoundary()

    useEffect(() => {
      // Reset the query error boundary
      queryErrorResetBoundary.reset()
    }, [queryErrorResetBoundary])

    return (
      <div>
        {error.message}
        <button
          onClick={() => {
            // Invalidate the route to reload the loader, and reset any router error boundaries
            router.invalidate()
          }}
        >
          retry
        </button>
      </div>
    )
  },
})

SSR Dehydrierung/Hydrierung

Tools, die dazu in der Lage sind, können mit den praktischen Dehydrierungs-/Hydrierungs-APIs von TanStack Router integriert werden, um dehydrierte Daten zwischen Server und Client zu shutteln und sie bei Bedarf zu rehydrieren. Gehen wir durch, wie das sowohl mit kritischen 3rd-Party-Daten als auch mit verzögerten 3rd-Party-Daten funktioniert.

Kritische Dehydrierung/Hydrierung

Für kritische Daten, die für das erste Rendering/Paint benötigt werden, unterstützt TanStack Router die Optionen dehydrate und hydrate bei der Konfiguration des Router. Diese Callbacks sind Funktionen, die auf dem Server und Client automatisch aufgerufen werden, wenn der Router normal dehydriert und hydriert, und es Ihnen ermöglichen, die dehydrierten Daten mit Ihren eigenen Daten zu ergänzen.

Die dehydrate-Funktion kann beliebige serialisierbare JSON-Daten zurückgeben, die zusammengeführt und in die auf den Client gesendete dehydrierte Nutzlast eingespeist werden.

Zum Beispiel dehydrieren und hydrieren wir einen TanStack Query QueryClient, damit unsere auf dem Server abgerufenen Daten zur Hydrierung auf dem Client verfügbar sind.

tsx
// src/router.tsx

export function createRouter() {
  // Make sure you create your loader client or similar data
  // stores inside of your `createRouter` function. This ensures
  // that your data stores are unique to each request and
  // always present on both server and client.
  const queryClient = new QueryClient()

  return createRouter({
    routeTree,
    // Optionally provide your loaderClient to the router context for
    // convenience (you can provide anything you want to the router
    // context!)
    context: {
      queryClient,
    },
    // On the server, dehydrate the loader client so the router
    // can serialize it and send it to the client for us
    dehydrate: () => {
      return {
        queryClientState: dehydrate(queryClient),
      }
    },
    // On the client, hydrate the loader client with the data
    // we dehydrated on the server
    hydrate: (dehydrated) => {
      hydrate(queryClient, dehydrated.queryClientState)
    },
    // Optionally, we can use `Wrap` to wrap our router in the loader client provider
    Wrap: ({ children }) => {
      return (
        <QueryClientProvider client={queryClient}>
          {children}
        </QueryClientProvider>
      )
    },
  })
}
// src/router.tsx

export function createRouter() {
  // Make sure you create your loader client or similar data
  // stores inside of your `createRouter` function. This ensures
  // that your data stores are unique to each request and
  // always present on both server and client.
  const queryClient = new QueryClient()

  return createRouter({
    routeTree,
    // Optionally provide your loaderClient to the router context for
    // convenience (you can provide anything you want to the router
    // context!)
    context: {
      queryClient,
    },
    // On the server, dehydrate the loader client so the router
    // can serialize it and send it to the client for us
    dehydrate: () => {
      return {
        queryClientState: dehydrate(queryClient),
      }
    },
    // On the client, hydrate the loader client with the data
    // we dehydrated on the server
    hydrate: (dehydrated) => {
      hydrate(queryClient, dehydrated.queryClientState)
    },
    // Optionally, we can use `Wrap` to wrap our router in the loader client provider
    Wrap: ({ children }) => {
      return (
        <QueryClientProvider client={queryClient}>
          {children}
        </QueryClientProvider>
      )
    },
  })
}
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.