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.
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.
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.
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.
// 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.
Werfen wir einen Blick auf ein realistischeres Beispiel mit TanStack Query.
// 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>
)
},
})
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.
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>
)
},
})
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.
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.
// 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>
)
},
})
}
Ihre wöchentliche Dosis JavaScript-Nachrichten. Jeden Montag kostenlos an über 100.000 Entwickler geliefert.